class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch = None self._diff_window = None self._init_ui() self._create_actions() self._create_toolbar() self._application.file_system_changed.connect( self._on_file_system_changed) ############################################## def _init_ui(self): # Table models are set in application self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) splitter = QtWidgets.QSplitter() splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() self._commit_table = QtWidgets.QTableView() for widget in (self._message_box, splitter): self._vertical_layout.addWidget(widget) for widget in (self._log_table, self._commit_table): splitter.addWidget(widget) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._show_patch) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def _create_actions(self): icon_loader = IconLoader() self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in ( self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action, ): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_file_system_changed(self, path): self._logger.info('File system changed {}'.format(path)) self._reload_repository() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _update_commit_table(self, index=None): if index is not None: index = index.row() else: index = 0 if index: self._current_revision = index log_table_model = self._log_table.model() commit1 = log_table_model[index] try: commit2 = log_table_model[index + 1] kwargs = dict(a=commit2, b=commit1) # Fixme: except IndexError: kwargs = dict(a=commit1) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _show_patch(self, index): self._current_patch = index.row() self._show_current_patch() ############################################## def _on_diff_window_closed(self): self._diff_window = None ############################################## @property def current_patch(self): return self._current_patch ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _show_current_patch(self): repository = self._application.repository if self._diff_window is None: from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() patch = self._diff[self._current_patch] delta = patch.delta if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + delta.new_file.path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (delta.old_file.path, delta.new_file.path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, delta.new_file.path) elif delta.status == git.GIT_DELTA_DELETED: paths = (delta.old_file.path, None) texts = [ repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id) ] metadatas = [ dict(path=delta.old_file.path, document_type='file', last_modification_date=None), dict(path=delta.new_file.path, document_type='file', last_modification_date=None) ] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) else: self._logger.info( 'revision {} Binary '.format(self._current_revision) + delta.new_file.path) # Fixme: show image pdf ... ############################################## @property def _last_path_index(self): return len(self._diff) - 1 ############################################## def previous_patch(self): if self._current_patch >= 1: self._current_patch -= 1 else: self._current_patch = self._last_path_index self._show_current_patch() ############################################## def next_patch(self): if self._current_patch < self._last_path_index: self._current_patch += 1 else: self._current_patch = 0 self._show_current_patch() ############################################## def reload_current_patch(self): self._show_current_patch()
class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') SHA_SHORTCUT_LENGTH = 8 ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) self._icon_loader = IconLoader() self.setWindowIcon(self._icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch_index = None self._diff_window = None self._review_note = None self._init_ui() self._create_actions() self._create_toolbar() self._application.directory_changed.connect(self._on_directory_changed) self._application.file_changed.connect(self._on_file_changed) ############################################## def _init_ui(self): # Table models are set in application central_widget = QtWidgets.QWidget(self) self.setCentralWidget(central_widget) top_vertical_layout = QtWidgets.QVBoxLayout(central_widget) self._message_box = MessageBox(self) top_vertical_layout.addWidget(self._message_box) horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) vertical_layout = QtWidgets.QVBoxLayout() horizontal_layout.addLayout(vertical_layout) horizontal_layout2 = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout2) button = QtWidgets.QPushButton('Diff') button.clicked.connect(self._on_diff_ab) horizontal_layout2.addWidget(button) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['edit-delete@svg']) horizontal_layout2.addWidget(button) self._diff_a = QtWidgets.QLineEdit(self) button.clicked.connect(lambda: self._diff_a.clear()) horizontal_layout2.addWidget(self._diff_a) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['media-playlist-repeat@svg']) button.clicked.connect(self._on_diff_exchange) horizontal_layout2.addWidget(button) self._diff_b = QtWidgets.QLineEdit(self) horizontal_layout2.addWidget(self._diff_b) button = QtWidgets.QPushButton() button.setIcon(self._icon_loader['edit-delete@svg']) button.clicked.connect(lambda: self._diff_b.clear()) horizontal_layout2.addWidget(button) # for widget in (self._diff_a, self._diff_b): # widget.editingFinished.connect(self._on_diff_ab) self._branch_name = QtWidgets.QLineEdit(self) self._branch_name.setReadOnly(True) vertical_layout.addWidget(self._branch_name) self._row_count = QtWidgets.QLabel('') vertical_layout.addWidget(self._row_count) row = 0 grid_layout = QtWidgets.QGridLayout() horizontal_layout.addLayout(grid_layout) label = QtWidgets.QLabel('Committer Filter') committer_filter = QtWidgets.QLineEdit() committer_filter.textChanged.connect(self._on_committer_filter_changed) for i, widget in enumerate((label, committer_filter)): grid_layout.addWidget(widget, row, i) row += 1 horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) label = QtWidgets.QLabel('Message Filter') message_filter = QtWidgets.QLineEdit() message_filter.textChanged.connect(self._on_message_filter_changed) for i, widget in enumerate((label, message_filter)): grid_layout.addWidget(widget, row, i) row += 1 horizontal_layout = QtWidgets.QHBoxLayout() top_vertical_layout.addLayout(horizontal_layout) label = QtWidgets.QLabel('SHA Filter') sha_filter = QtWidgets.QLineEdit() sha_filter.textChanged.connect(self._on_sha_filter_changed) for i, widget in enumerate((label, sha_filter)): grid_layout.addWidget(widget, row, i) row += 1 splitter = QtWidgets.QSplitter() top_vertical_layout.addWidget(splitter) splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() splitter.addWidget(self._log_table) bottom_widget = QtWidgets.QWidget() splitter.addWidget(bottom_widget) bottom_horizontal_layout = QtWidgets.QHBoxLayout() bottom_widget.setLayout(bottom_horizontal_layout) vertical_layout = QtWidgets.QVBoxLayout() bottom_horizontal_layout.addLayout(vertical_layout) self._commit_table = QtWidgets.QTableView() vertical_layout.addWidget(self._commit_table) vertical_layout = QtWidgets.QVBoxLayout() bottom_horizontal_layout.addLayout(vertical_layout) self._commit_sha = QtWidgets.QLineEdit() self._commit_sha.setReadOnly(True) vertical_layout.addWidget(self._commit_sha) self._parent_labels = [] for i in range(2): horizontal_layout = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout) button = QtWidgets.QPushButton('Go') button.clicked.connect( lambda state, index=i: self._on_go_clicked(index)) horizontal_layout.addWidget(button) parent = QtWidgets.QLineEdit() parent.setReadOnly(True) horizontal_layout.addWidget(parent) self._parent_labels.append(parent) self._review_comment = QtWidgets.QTextEdit() vertical_layout.addWidget(self._review_comment) horizontal_layout = QtWidgets.QHBoxLayout() vertical_layout.addLayout(horizontal_layout) save_button = QtWidgets.QPushButton('Save') save_button.clicked.connect(self._on_save_review) horizontal_layout.addItem( QtWidgets.QSpacerItem(0, 10, QSizePolicy.Expanding)) horizontal_layout.addWidget(save_button) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._on_clicked_table) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def finish_table_connections(self): self._log_table.selectionModel().currentRowChanged.connect( self._update_commit_table) #!# Fixme: reopen diff viewer window when repository change #!# self._commit_table.selectionModel().currentRowChanged.connect(self._on_clicked_table) ############################################## def _create_actions(self): self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in ( self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(self._icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action, ): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_directory_changed(self, path): self._logger.info(path) self._reload_repository() self._diff = self._application.repository.diff(**self._diff_kwargs) if self._diff_window is not None: if self.number_of_patches: self._current_patch_index = 0 self._diff_window.update_patch_index() self.reload_current_patch() else: self._diff_window.close() ############################################## def _on_file_changed(self, path): self._logger.info(path) repository = self._application.repository if path == repository.join_repository_path(repository.INDEX_PATH): self._diff = self._application.repository.diff(**self._diff_kwargs) else: message = 'File {} changed'.format(path) self.show_message(message) self.reload_current_patch() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _reset_parent(self): self._current_commit = None for parent in self._parent_labels: parent.clear() ############################################## def _update_commit_table(self, index=None): self._commit_sha.clear() self._reset_parent() if self._review_note is not None: self._on_save_review() self._review_note = None self._review_comment.clear() if index is not None: index = self._application.log_table_filter.mapToSource(index) index = index.row() else: index = 0 # Diff a=old b=new commit if index: self._current_revision = index # log_table_model = self._log_table.model() log_table_model = self._application.log_table_model self._current_commit = log_table_model[index] sha = self._current_commit.hex self._commit_sha.setText('Commit: {} / {}'.format( sha[:self.SHA_SHORTCUT_LENGTH], sha)) if len(self._current_commit.parents) > len(self._parent_labels): self.show_message('Fixme: More than 2 parents') for commit, parent_label in zip(self._current_commit.parents, self._parent_labels): parent_label.setText('Parent: {} ({})'.format( commit.hex[:self.SHA_SHORTCUT_LENGTH], commit.message)) parent_label.setCursorPosition(0) self._review_note = self._application.review[sha] if self._review_note is not None: self._review_comment.setText(self._review_note.text) else: self._review_note = ReviewNote(sha) commit_a = self._current_commit.parents[0] # take first parent kwargs = dict(a=commit_a, b=self._current_commit) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff_kwargs = kwargs self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _on_clicked_table(self, index): # called when a commit row is clicked self._logger.info('') self._current_patch_index = index.row() self.reload_current_patch() ############################################## @property def current_patch_index(self): return self._current_patch_index ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _create_diff_viewer_window(self): self._logger.info("Open Diff Viewer") from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow repository = self._application.repository self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() ############################################## def _on_diff_window_closed(self): self._application.unwatch_files() # Fixme: only current patch ! self._diff_window = None self._logger.info("Diff Viewer closed") ############################################## def _show_patch(self, patch): self._logger.info('') self._application.unwatch_files() if self._diff_window is None: self._create_diff_viewer_window() delta = patch.delta old_path = delta.old_file.path new_path = delta.new_file.path if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + new_path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (old_path, new_path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, new_path) elif delta.status == git.GIT_DELTA_DELETED: paths = (old_path, None) repository = self._application.repository texts = [ repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id) ] metadatas = [ dict(path=old_path, document_type='file', last_modification_date=None), dict(path=new_path, document_type='file', last_modification_date=None), ] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) self._application.watch(new_path) else: self._logger.info( 'revision {} Binary '.format(self._current_revision) + new_path) # Fixme: show image pdf ... # Monitor file change ############################################## @property def _last_path_index(self): return len(self._diff) - 1 ############################################## def _next_previous_patch(self, forward): if forward: if self._current_patch_index < self._last_path_index: patch_index = self._current_patch_index + 1 else: patch_index = 0 else: if self._current_patch_index >= 1: patch_index = self._current_patch_index - 1 else: patch_index = self._last_path_index self._current_patch_index = patch_index patch = self._diff[patch_index] self._show_patch(patch) ############################################## def previous_patch(self): self._next_previous_patch(forward=False) ############################################## def next_patch(self): self._next_previous_patch(forward=True) ############################################## def reload_current_patch(self): if self._current_patch_index is not None: patch = self._diff[self._current_patch_index] self._show_patch(patch) ############################################## def _on_committer_filter_changed(self, text): log_table_filter = self._application.log_table_filter log_table_filter.setFilterRegExp( QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn( LogTableModel.COLUMN_ENUM.committer) self._on_log_table_filter_changed( ) # seems to just work, no need to connect signal ############################################## def _on_message_filter_changed(self, text): log_table_filter = self._application.log_table_filter log_table_filter.setFilterRegExp( QRegExp(text, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn(LogTableModel.COLUMN_ENUM.message) self._on_log_table_filter_changed() ############################################## def _on_sha_filter_changed(self, text): log_table_filter = self._application.log_table_filter if text: # Fixme: ??? # regexp = '^' + text regexp = text else: regexp = '' log_table_filter.setFilterRegExp( QRegExp(regexp, Qt.CaseInsensitive, QRegExp.FixedString)) log_table_filter.setFilterKeyColumn(LogTableModel.COLUMN_ENUM.sha) self._on_log_table_filter_changed() ############################################## def _on_log_table_filter_changed(self): log_table_filter = self._application.log_table_filter self._row_count.setText('{} commits'.format( log_table_filter.rowCount())) ############################################## def _on_save_review(self): self._review_note.text = self._review_comment.toPlainText() self._application.review.add(self._review_note) self._application.review.save() ############################################## def _on_go_clicked(self, parent_index): if self._current_commit is not None: try: parent_commit_sha = str( self._current_commit.parent_ids[parent_index]) index = self._application.log_table_model.find_commit( parent_commit_sha) if index is not None: self._logger.info('Found parent commit {} {}'.format( parent_index, parent_commit_sha)) self._log_table.selectRow(index.row()) except IndexError: pass ############################################## def _on_diff_exchange(self): diff_a = self._diff_a.text() diff_b = self._diff_b.text() self._diff_a.setText(diff_b) self._diff_b.setText(diff_a) self._on_diff_ab() ############################################## def _on_diff_ab(self): diff_a = self._diff_a.text() diff_b = self._diff_b.text() if diff_a and diff_b: try: kwargs = dict(a=diff_a, b=diff_b) self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() except ValueError as e: self._logger.warning(e) self.show_message(str(e))
class LogBrowserMainWindow(MainWindowBase): _logger = _module_logger.getChild('LogBrowserMainWindow') ############################################## def __init__(self, parent=None): super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) icon_loader = IconLoader() self.setWindowIcon(icon_loader['code-review@svg']) self._current_revision = None self._diff = None self._current_patch_index = None self._diff_window = None self._init_ui() self._create_actions() self._create_toolbar() self._application.directory_changed.connect(self._on_directory_changed) self._application.file_changed.connect(self._on_file_changed) ############################################## def _init_ui(self): # Table models are set in application self._central_widget = QtWidgets.QWidget(self) self.setCentralWidget(self._central_widget) self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) self._message_box = MessageBox(self) splitter = QtWidgets.QSplitter() splitter.setOrientation(Qt.Vertical) self._log_table = QtWidgets.QTableView() self._commit_table = QtWidgets.QTableView() for widget in (self._message_box, splitter): self._vertical_layout.addWidget(widget) for widget in (self._log_table, self._commit_table): splitter.addWidget(widget) table = self._log_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) # table.setSortingEnabled(True) table.clicked.connect(self._update_commit_table) table = self._commit_table table.setSelectionMode(QtWidgets.QTableView.SingleSelection) table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) table.verticalHeader().setVisible(False) table.setShowGrid(False) table.setSortingEnabled(True) table.clicked.connect(self._on_clicked_table) # horizontal_header = table_view.horizontalHeader() # horizontal_header.setMovable(True) ############################################## def finish_table_connections(self): self._log_table.selectionModel().currentRowChanged.connect(self._update_commit_table) #!# Fixme: reopen diff viewer window when repository change #!# self._commit_table.selectionModel().currentRowChanged.connect(self._on_clicked_table) ############################################## def _create_actions(self): icon_loader = IconLoader() self._stagged_mode_action = \ QtWidgets.QAction('Stagged', self, toolTip='Stagged Mode', shortcut='Ctrl+1', checkable=True, ) self._not_stagged_mode_action = \ QtWidgets.QAction('Not Stagged', self, toolTip='Not Stagged Mode', shortcut='Ctrl+2', checkable=True, ) self._all_change_mode_action = \ QtWidgets.QAction('All', self, toolTip='All Mode', shortcut='Ctrl+3', checkable=True, ) self._action_group = QtWidgets.QActionGroup(self) self._action_group.triggered.connect(self._update_working_tree_diff) for action in (self._all_change_mode_action, self._stagged_mode_action, self._not_stagged_mode_action, ): self._action_group.addAction(action) self._all_change_mode_action.setChecked(True) self._reload_action = \ QtWidgets.QAction(icon_loader['view-refresh@svg'], 'Refresh', self, toolTip='Refresh', shortcut='Ctrl+R', triggered=self._reload_repository, ) ############################################## def _create_toolbar(self): self._tool_bar = self.addToolBar('Diff on Working Tree') for item in self._action_group.actions(): self._tool_bar.addAction(item) for item in (self._reload_action,): self._tool_bar.addAction(item) ############################################## def init_menu(self): super(LogBrowserMainWindow, self).init_menu() ############################################## def show_message(self, message=None, timeout=0, warn=False): """ Hides the normal status indications and displays the given message for the specified number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed until the clearMessage() slot is called or until the showMessage() slot is called again to change the message. Note that showMessage() is called to show temporary explanations of tool tip texts, so passing a timeout of 0 is not sufficient to display a permanent message. """ if warn: self._message_box.push_message(message) else: status_bar = self.statusBar() if message is None: status_bar.clearMessage() else: status_bar.showMessage(message, timeout) ############################################## def _on_directory_changed(self, path): self._logger.info(path) self._reload_repository() self._diff = self._application.repository.diff(**self._diff_kwargs) if self._diff_window is not None: if self.number_of_patches: self._current_patch_index = 0 self._diff_window.update_patch_index() self.reload_current_patch() else: self._diff_window.close() ############################################## def _on_file_changed(self, path): self._logger.info(path) repository = self._application.repository if path == repository.join_repository_path(repository.INDEX_PATH): self._diff = self._application.repository.diff(**self._diff_kwargs) else: message = 'File {} changed'.format(path) self.show_message(message) self.reload_current_patch() ############################################## def _reload_repository(self): self._logger.info('Reload signal') index = self._log_table.currentIndex() self._application.reload_repository() if index.row() != -1: self._logger.info("Index is {}".format(index.row())) self._log_table.setCurrentIndex(index) # Fixme: ??? # self._update_working_tree_diff() self.show_working_tree_diff() else: self.show_working_tree_diff() ############################################## def show_working_tree_diff(self): self._logger.info('Show WT') log_model = self._log_table.model() if log_model.rowCount(): top_index = log_model.index(0, 0) self._log_table.setCurrentIndex(top_index) self._update_working_tree_diff() ############################################## def _update_working_tree_diff(self): # Check log table is on working tree if self._log_table.currentIndex().row() == 0: self._update_commit_table() ############################################## def _update_commit_table(self, index=None): if index is not None: index = index.row() else: index = 0 if index: self._current_revision = index log_table_model = self._log_table.model() commit1 = log_table_model[index] try: commit2 = log_table_model[index +1] kwargs = dict(a=commit2, b=commit1) # Fixme: except IndexError: kwargs = dict(a=commit1) else: # working directory self._current_revision = None if self._stagged_mode_action.isChecked(): # Changes between the index and your last commit kwargs = dict(a='HEAD', cached=True) elif self._not_stagged_mode_action.isChecked(): # Changes in the working tree not yet staged for the next commit kwargs = {} elif self._all_change_mode_action.isChecked(): # Changes in the working tree since your last commit kwargs = dict(a='HEAD') self._diff_kwargs = kwargs self._diff = self._application.repository.diff(**kwargs) commit_table_model = self._commit_table.model() commit_table_model.update(self._diff) self._commit_table.resizeColumnsToContents() ############################################## def _on_clicked_table(self, index): # called when a commit row is clicked self._logger.info('') self._current_patch_index = index.row() self.reload_current_patch() ############################################## @property def current_patch_index(self): return self._current_patch_index ############################################## @property def number_of_patches(self): return len(self._diff) ############################################## def _create_diff_viewer_window(self): self._logger.info("Open Diff Viewer") from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow repository = self._application.repository self._diff_window = DiffViewerMainWindow(self, repository=repository) self._diff_window.closed.connect(self._on_diff_window_closed) self._diff_window.showMaximized() ############################################## def _on_diff_window_closed(self): self._application.unwatch_files() # Fixme: only current patch ! self._diff_window = None self._logger.info("Diff Viewer closed") ############################################## def _show_patch(self, patch): self._logger.info("") self._application.unwatch_files() if self._diff_window is None: self._create_diff_viewer_window() delta = patch.delta old_path = delta.old_file.path new_path = delta.new_file.path if not delta.is_binary: self._logger.info('revision {} '.format(self._current_revision) + new_path) # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) # for hunk in delta.hunks: # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): paths = (old_path, new_path) elif delta.status == git.GIT_DELTA_ADDED: paths = (None, new_path) elif delta.status == git.GIT_DELTA_DELETED: paths = (old_path, None) repository = self._application.repository texts = [repository.file_content(blob_id) for blob_id in (delta.old_file.id, delta.new_file.id)] metadatas = [dict(path=old_path, document_type='file', last_modification_date=None), dict(path=new_path, document_type='file', last_modification_date=None)] self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) self._application.watch(new_path) else: self._logger.info('revision {} Binary '.format(self._current_revision) + new_path) # Fixme: show image pdf ... # Monitor file change ############################################## @property def _last_path_index(self): return len(self._diff) -1 ############################################## def _next_previous_patch(self, forward): if forward: if self._current_patch_index < self._last_path_index: patch_index = self._current_patch_index + 1 else: patch_index = 0 else: if self._current_patch_index >= 1: patch_index = self._current_patch_index - 1 else: patch_index = self._last_path_index self._current_patch_index = patch_index patch = self._diff[patch_index] self._show_patch(patch) ############################################## def previous_patch(self): self._next_previous_patch(forward=False) ############################################## def next_patch(self): self._next_previous_patch(forward=True) ############################################## def reload_current_patch(self): if self._current_patch_index is not None: patch = self._diff[self._current_patch_index] self._show_patch(patch)