From fa4b9d848deb969a52e3f0d84b345c49f0c14c7e Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 21 Dec 2007 01:57:21 -0800 Subject: [PATCH] Consolidated model classes into a single class All views now share the same model. Added obligatory qobserver destructor to handle unregistering observers from their subjects. Signed-off by: David Aguilar --- py/controllers.py | 87 ++++++++++++++------- py/createbranchcontroller.py | 1 + py/model.py | 9 ++- py/models.py | 180 ++++++++++++++++++++++--------------------- py/qobserver.py | 9 +++ py/repobrowsercontroller.py | 5 +- 6 files changed, 171 insertions(+), 120 deletions(-) diff --git a/py/controllers.py b/py/controllers.py index c1ff136..f1317bc 100644 --- a/py/controllers.py +++ b/py/controllers.py @@ -7,8 +7,6 @@ from qobserver import QObserver import cmds import utils import qtutils -from models import GitRepoBrowserModel -from models import GitCreateBranchModel from views import GitCommitBrowser from views import GitBranchDialog from views import GitCreateBranchDialog @@ -22,6 +20,22 @@ class GitController (QObserver): def __init__ (self, model, view): QObserver.__init__ (self, model, view) + # chdir to the root of the git tree. This is critical + # to being able to properly use the git porcelain. + cdup = cmds.git_show_cdup() + if cdup: os.chdir (cdup) + + # The diff-display context menu + self.__menu = None + self.__staged_diff_in_view = True + + # Diff display context menu + view.displayText.controller = self + view.displayText.contextMenuEvent = self.__menu_event + + # Default to creating a new commit (i.e. not an amend commit) + view.newCommitRadio.setChecked (True) + # Binds a specific model attribute to a view widget, # and vice versa. self.model_to_view (model, 'commitmsg', 'commitText') @@ -64,9 +78,11 @@ class GitController (QObserver): view.exportPatches, view.cherryPick,) + self.add_signals ('itemClicked (QListWidgetItem *)', + view.stagedList, view.unstagedList,) + self.add_signals ('itemSelectionChanged()', - view.stagedList, - view.unstagedList,) + view.stagedList, view.unstagedList,) # App cleanup self.connect ( qtutils.qapp(), @@ -124,19 +140,6 @@ class GitController (QObserver): 'cherryPick': self.cb_cherry_pick, }) - # chdir to the root of the git tree. This is critical - # to being able to properly use the git porcelain. - cdup = cmds.git_show_cdup() - if cdup: os.chdir (cdup) - - # The diff-display context menu - self.__menu = None - view.displayText.controller = self - view.displayText.contextMenuEvent = self.__menu_event - - # Default to creating a new commit (i.e. not an amend commit) - view.newCommitRadio.setChecked (True) - # Initialize the GUI self.cb_rescan (model) @@ -171,14 +174,13 @@ class GitController (QObserver): # CALLBACKS ##################################################################### - def cb_branch_create (self, ugit_model): - model = GitCreateBranchModel() + def cb_branch_create (self, model): view = GitCreateBranchDialog (self.view) controller = GitCreateBranchController (model, view) view.show() result = view.exec_() if result == QDialog.Accepted: - self.cb_rescan (ugit_model) + self.cb_rescan (model) def cb_branch_delete (self, model): dlg = GitBranchDialog(self.view, branches=cmds.git_branch()) @@ -280,9 +282,13 @@ class GitController (QObserver): qtutils.set_clipboard (sha1) def cb_copy (self): - self.view.displayText.copy() + cursor = self.view.displayText.textCursor() + selection = cursor.selection().toPlainText() + qtutils.set_clipboard (selection) - def cb_diff_staged (self, model): + # use *args to handle being called from different signals + def cb_diff_staged (self, model, *args): + self.__staged_diff_in_view = True list_widget = self.view.stagedList row, selected = qtutils.get_selected_row (list_widget) @@ -300,7 +306,9 @@ class GitController (QObserver): self.view.displayText.setText (pre + diff) - def cb_diff_unstaged (self, model): + # use *args to handle being called from different signals + def cb_diff_unstaged (self, model, *args): + self.__staged_diff_in_view = False list_widget = self.view.unstagedList row, selected = qtutils.get_selected_row (list_widget) if not selected: @@ -441,7 +449,33 @@ class GitController (QObserver): self.__show_command (output, model) def cb_stage_hunk (self): - print "STAGING HUNK" + + list_widget = self.view.unstagedList + row, selected = qtutils.get_selected_row (list_widget) + if not selected: return + + filename = model.get_uncommitted_item (row) + + cursor = self.view.displayText.textCursor() + offset = cursor.position() + offset -= utils.HEADER_LENGTH + 1 + if offset < 0: return + + selection = cursor.selection().toPlainText() + + num_selected_lines = selection.count (os.linesep) + has_selection = selection and nb_selected_lines > 0 + + + if has_selection: + print "\nNUM_LINES", num_selected_lines + print "SELECTION:\n", selection + else: + print "SELECTION:\n", seleciton + + print 'POSITION:', cursor.position() + + def cb_stage_selected (self, model): '''Use "git add" to add items to the git index. @@ -494,14 +528,15 @@ class GitController (QObserver): def __browse_branch (self, branch): if not branch: return - model = GitRepoBrowserModel (branch) + model = self.model + model.set_branch (branch) view = GitCommitBrowser() controller = GitRepoBrowserController(model, view) view.show() view.exec_() def __menu_about_to_show (self): - self.__stage_hunk_action.setEnabled (True) + self.__stage_hunk_action.setEnabled (not self.__staged_diff_in_view) def __menu_event (self, event): self.__menu_setup() diff --git a/py/createbranchcontroller.py b/py/createbranchcontroller.py index e387a02..b9a3471 100644 --- a/py/createbranchcontroller.py +++ b/py/createbranchcontroller.py @@ -36,6 +36,7 @@ class GitCreateBranchController (QObserver): lambda(m): self.__display_model (m), }) + model.init_branch_data() self.__display_model (model) ###################################################################### diff --git a/py/model.py b/py/model.py index 241f68f..d38cdb3 100644 --- a/py/model.py +++ b/py/model.py @@ -11,14 +11,17 @@ class Observable(object): return self.__notify def set_notify(self, notify=True): self.__notify = notify - def add_observer(self,observer): - self.__observers.append(observer) + def add_observer(self, observer): + if observer not in self.__observers: + self.__observers.append(observer) + def remove_observer(self, observer): + if observer in self.__observers: + self.__observers.remove (observer) def notify_observers(self, *attr): if not self.__notify: return for observer in self.__observers: observer.notify(*attr) - class Model(Observable): '''Creates a generic model object with attributes specified as a name:value dictionary. diff --git a/py/models.py b/py/models.py index 06c9d00..99b45ec 100644 --- a/py/models.py +++ b/py/models.py @@ -7,87 +7,83 @@ from model import Model class GitModel(Model): def __init__ (self): Model.__init__ (self, { - 'commitmsg': '', - 'staged': [], - 'unstaged': [], - 'untracked': [], - 'name': cmds.git_config('user.name'), - 'email': cmds.git_config('user.email'), - }) - - def add_signoff (self): - '''Adds a standard Signed-off by: tag to the end - of the current commit message.''' - - msg = self.get_commitmsg() - signoff = ('Signed-off by: %s <%s>' - % (self.get_name(), self.get_email())) - - if signoff not in msg: - self.set_commitmsg (msg + '\n\n' + signoff) - - def set_latest_commitmsg (self): - '''Queries git for the latest commit message and sets it in - self.commitmsg.''' - commit_msg = [] - commit_lines = cmds.git_show ('HEAD').split ('\n') - for idx, msg in enumerate (commit_lines): - if idx < 4: continue - msg = msg.lstrip() - if msg.startswith ('diff --git'): - commit_msg.pop() - break - commit_msg.append (msg) - self.set_commitmsg ('\n'.join (commit_msg).rstrip()) - -class GitRepoBrowserModel (Model): - def __init__ (self, branch=''): - Model.__init__ (self, { - 'directory': '', - 'branch': branch, - - # These are parallel lists - 'files': [], - 'sha1s': [], - 'types': [], - - # All items below here are re-calculated in - # setup_items() - 'directories': [], - 'directory_entries': {}, - - # These are also parallel lists - 'item_names': [], - 'item_sha1s': [], - 'item_types': [], - }) - self.reset() - - def reset (self): - # Collect data for the model - tree_info = cmds.git_ls_tree (self.get_branch()) - - types = map ( lambda (x): x[1], tree_info ) - sha1s = map ( lambda (x): x[2], tree_info ) - files = map ( lambda (x): x[3], tree_info ) - - self.add_types (*types) - self.add_files (*files) - self.add_sha1s (*sha1s) + # =========================================== + # Used in various places + # =========================================== + 'branch': '', + + # =========================================== + # Used primarily by the main UI + # =========================================== + 'name': cmds.git_config('user.name'), + 'email': cmds.git_config('user.email'), + 'commitmsg': '', + 'staged': [], + 'unstaged': [], + 'untracked': [], + + # =========================================== + # Used by the create branch dialog + # =========================================== + 'revision': '', + 'local_branches': cmds.git_branch (remote=False), + 'remote_branches': cmds.git_branch (remote=True), + 'tags': cmds.git_tag(), + + # =========================================== + # Used by the repo browser + # =========================================== + 'directory': '', + + # These are parallel lists + 'files': [], + 'sha1s': [], + 'types': [], + + # All items below here are re-calculated in + # init_browser_data() + 'directories': [], + 'directory_entries': {}, + + # These are also parallel lists + 'item_names': [], + 'item_sha1s': [], + 'item_types': [], + }) + + + def init_branch_data (self): + remote_branches = cmds.git_branch (remote=True) + local_branches = cmds.git_branch (remote=False) + tags = cmds.git_tag() - self.reset_items() + self.set_branch ('') + self.set_revision ('') + self.set_local_branches (local_branches) + self.set_remote_branches (remote_branches) + self.set_tags (tags) - def reset_items (self): + def init_browser_data (self): '''This scans over self.(files, sha1s, types) to generate directories, directory_entries, itmes, item_sha1s, and item_types.''' + # Collect data for the model + if not self.get_branch(): return + self.item_names = [] self.item_sha1s = [] self.item_types = [] self.directories = [] self.directory_entries = {} + # Lookup the tree info + tree_info = cmds.git_ls_tree (self.get_branch()) + + self.set_types (map ( lambda (x): x[1], tree_info )) + self.set_sha1s (map ( lambda (x): x[2], tree_info )) + self.set_files (map ( lambda (x): x[3], tree_info )) + if self.directory: self.directories.append ('..') dir_entries = self.directory_entries @@ -128,24 +124,30 @@ class GitRepoBrowserModel (Model): self.item_sha1s.append (self.sha1s[idx]) self.item_types.append (self.types[idx]) -class GitCreateBranchModel (Model): - def __init__ (self): - Model.__init__ (self, { - 'branch': '', - 'revision': '', - 'local_branches': [], - 'remote_branches': [], - 'tags': [], - }) - self.reset() - - def reset (self): - remote_branches = cmds.git_branch (remote=True) - local_branches = cmds.git_branch (remote=False) - tags = cmds.git_tag() + def add_signoff (self): + '''Adds a standard Signed-off by: tag to the end + of the current commit message.''' - self.set_branch ('') - self.set_revision ('') - self.set_local_branches (local_branches) - self.set_remote_branches (remote_branches) - self.set_tags (tags) + msg = self.get_commitmsg() + signoff = ('Signed-off by: %s <%s>' + % (self.get_name(), self.get_email())) + + if signoff not in msg: + self.set_commitmsg (msg + '\n\n' + signoff) + + def set_latest_commitmsg (self): + '''Queries git for the latest commit message and sets it in + self.commitmsg.''' + commit_msg = [] + commit_lines = cmds.git_show ('HEAD').split ('\n') + for idx, msg in enumerate (commit_lines): + if idx < 4: continue + msg = msg.lstrip() + if msg.startswith ('diff --git'): + commit_msg.pop() + break + commit_msg.append (msg) + self.set_commitmsg ('\n'.join (commit_msg).rstrip()) + + def get_uncommitted_item (self, row): + return (self.get_unstaged() + self.get_untracked())[row] diff --git a/py/qobserver.py b/py/qobserver.py index 0228c16..afcc287 100644 --- a/py/qobserver.py +++ b/py/qobserver.py @@ -19,6 +19,15 @@ class QObserver (Observer, QObject): self.__callbacks = {} self.__model_to_view = {} self.__view_to_model = {} + + def __del__ (self): + self.model.remove_observer (self) + self.model = None + self.view = None + + del self.__actions + del self.__model_to_view + del self.__view_to_model def connect (self, obj, signal_str, *args): '''Convenience function so that subclasses do not have diff --git a/py/repobrowsercontroller.py b/py/repobrowsercontroller.py index bc3d2f3..57cc29f 100644 --- a/py/repobrowsercontroller.py +++ b/py/repobrowsercontroller.py @@ -27,7 +27,8 @@ class GitRepoBrowserController (QObserver): 'itemDoubleClicked(QListWidgetItem*)', lambda(x): self.cb_item_double_clicked (model)) - self.__display_items (model) + # Start at the root of the tree + model.set_directory('') ###################################################################### # ACTIONS @@ -36,7 +37,7 @@ class GitRepoBrowserController (QObserver): def action_directory_changed (self, model): '''This is called in response to a change in the the model's directory.''' - model.reset_items() + model.init_browser_data() self.__display_items (model) ###################################################################### -- 2.11.4.GIT