Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
712ffe9461 | ||
|
|
c0f637696b | ||
|
|
8c057f23c4 | ||
|
|
2eb5c7c169 | ||
|
|
cd5575ceeb | ||
|
|
53d3678cad | ||
|
|
bfd3ffe12d | ||
|
|
5b8a48531d | ||
|
|
e2fe2c8220 | ||
|
|
1a36ed3608 | ||
|
|
c51ebdf897 | ||
|
|
98c97ae89b | ||
|
|
10454e65d3 | ||
|
|
406e9111e8 | ||
|
|
188e72390b | ||
|
|
0c1cc38303 | ||
|
|
2c3cf4d4d6 | ||
|
|
1adc310e0a | ||
|
|
a92773e902 | ||
|
|
0e6995fce5 | ||
|
|
d7987e6fc3 | ||
|
|
98ee623ffe | ||
|
|
9b75fd9e69 | ||
|
|
d0392ad426 | ||
|
|
e44710ab00 | ||
|
|
5d97efb930 | ||
|
|
a2fef119c7 | ||
|
|
f81b6c932a | ||
|
|
7f3470a3bf | ||
|
|
84c5849dd8 | ||
|
|
dae657e7aa | ||
|
|
092566c334 | ||
|
|
9674055ba5 | ||
|
|
2f015d3e53 | ||
|
|
1b1aeb289e | ||
|
|
dc9bdddb3c | ||
|
|
93164bf942 | ||
|
|
e9ec0b6eda | ||
|
|
d7a7e677ad | ||
|
|
2f6c2cd72c | ||
|
|
6b395315be | ||
|
|
e7eb21e7f5 | ||
|
|
7269d006f7 | ||
|
|
4070e6a742 | ||
|
|
fc1e292125 | ||
|
|
b90d34fcf0 | ||
|
|
3d9819f68b | ||
|
|
39ad9c8ee8 | ||
|
|
69f90dae5a | ||
|
|
c147b36506 | ||
|
|
8489f6419b | ||
|
|
000b272486 | ||
|
|
f0c3bbeede | ||
|
|
99e67fd27a | ||
|
|
8195d3e211 | ||
|
|
1a61315114 | ||
|
|
b1de8addfa | ||
|
|
fa8ed6b9c3 | ||
|
|
e849844421 | ||
|
|
a1e9785974 | ||
|
|
dc2b920794 | ||
|
|
48fc0e41e5 | ||
|
|
0fb081a33f | ||
|
|
e7dcc99112 |
@@ -66,7 +66,7 @@ You don't need to install cola to run it, though.
|
||||
bin/git-cola
|
||||
bin/git-dag
|
||||
|
||||
## MAC OS X INSTALLTION INSTRUCTIONS
|
||||
## MAC OS X
|
||||
|
||||
Whether you install cola yourself with `make install` or
|
||||
use the `git-cola.app` bundle, you will need to install
|
||||
|
||||
+10
-2
@@ -4,9 +4,16 @@
|
||||
import glob
|
||||
import optparse
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
import sys
|
||||
|
||||
# Make homebrew work by default
|
||||
if platform.system() == 'Darwin':
|
||||
homebrew_mods = '/usr/local/lib/python'
|
||||
if os.path.isdir(homebrew_mods):
|
||||
sys.path.append(homebrew_mods)
|
||||
|
||||
try:
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
@@ -131,6 +138,7 @@ class ColaApplication(object):
|
||||
def parse_args(context):
|
||||
args = sys.argv[1:]
|
||||
builtins = set(('branch',
|
||||
'browse',
|
||||
'classic',
|
||||
'dag',
|
||||
'fetch',
|
||||
@@ -240,10 +248,10 @@ def main(context):
|
||||
# Show the GUI
|
||||
if context == 'branch':
|
||||
view = create_new_branch()
|
||||
elif context == 'git-dag' or context == 'dag':
|
||||
elif context in ('git-dag', 'dag'):
|
||||
ctl = git_dag(model, opts=opts, args=args)
|
||||
view = ctl.view
|
||||
elif context == 'classic':
|
||||
elif context in ('classic', 'browse'):
|
||||
view = cola_classic(update=False)
|
||||
# TODO: the calls to update_status() can be done asynchronously
|
||||
# by hooking into the message_updated notification.
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import sys
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
from cola import core
|
||||
@@ -28,7 +29,7 @@ class BookmarksDialog(standard.Dialog):
|
||||
|
||||
self.resize(494, 238)
|
||||
self.setWindowTitle(self.tr('Bookmarks'))
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
self.layt = QtGui.QVBoxLayout(self)
|
||||
self.layt.setMargin(defs.margin)
|
||||
self.layt.setSpacing(defs.spacing)
|
||||
|
||||
@@ -7,10 +7,9 @@ import cola.utils
|
||||
import cola.difftool
|
||||
|
||||
from cola import gitcmds
|
||||
from cola import qtutils
|
||||
from cola import signals
|
||||
from cola.widgets.selectcommits import select_commits
|
||||
from cola.classic.view import RepoDialog
|
||||
from cola.classic.view import Browser
|
||||
from cola.classic.model import GitRepoModel
|
||||
from cola.classic.model import GitRepoEntryManager
|
||||
from cola.compat import set
|
||||
@@ -18,15 +17,15 @@ from cola.compat import set
|
||||
|
||||
def widget(parent, update=True):
|
||||
"""Return a widget for immediate use."""
|
||||
view = RepoDialog(parent, update=update)
|
||||
view = Browser(parent, update=update)
|
||||
view.tree.setModel(GitRepoModel(view.tree))
|
||||
ctl = ClassicController(view.tree)
|
||||
view.ctl = ClassicController(view.tree)
|
||||
return view
|
||||
|
||||
|
||||
def cola_classic(update=True):
|
||||
"""Launch a new cola classic session."""
|
||||
view = widget(qtutils.active_window(), update=update)
|
||||
view = widget(None, update=update)
|
||||
view.show()
|
||||
return view
|
||||
|
||||
@@ -65,7 +64,7 @@ class ClassicController(QtCore.QObject):
|
||||
def difftool_predecessor(self, paths):
|
||||
"""Prompt for an older commit and launch difftool against it."""
|
||||
args = ['--'] + paths
|
||||
revs, summaries = gitcmds.log_helper(all=True, extra_args=args)
|
||||
revs, summaries = gitcmds.log_helper(all=False, extra_args=args)
|
||||
commits = select_commits('Select Previous Version',
|
||||
revs, summaries, multiselect=False)
|
||||
if not commits:
|
||||
|
||||
@@ -290,7 +290,7 @@ class GitRepoInfoTask(QRunnable):
|
||||
if not self._data:
|
||||
log_line = cola.model().git.log('-1', '--', self.path,
|
||||
M=True,
|
||||
all=True,
|
||||
all=False,
|
||||
no_color=True,
|
||||
pretty='format:%ar%x01%s%x01%an')
|
||||
if log_line:
|
||||
|
||||
+9
-91
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
@@ -15,10 +14,9 @@ from cola.widgets import standard
|
||||
from cola.classic.model import GitRepoNameItem
|
||||
|
||||
|
||||
class RepoDialog(standard.Dialog):
|
||||
class Browser(standard.Widget):
|
||||
def __init__(self, parent, update=True):
|
||||
standard.Dialog.__init__(self, parent)
|
||||
self.setObjectName('classic')
|
||||
super(Browser, self).__init__(parent)
|
||||
self.tree = RepoTreeView(self)
|
||||
self.mainlayout = QtGui.QHBoxLayout()
|
||||
self.setLayout(self.mainlayout)
|
||||
@@ -48,22 +46,18 @@ class RepoDialog(standard.Dialog):
|
||||
|
||||
self.setToolTip(msg)
|
||||
|
||||
title = '%s [%s]' % (self.model.project, branch)
|
||||
title = '%s: %s' % (self.model.project, branch)
|
||||
if self.mode == self.model.mode_amend:
|
||||
title += ' ** amending **'
|
||||
self.setWindowTitle(title)
|
||||
|
||||
|
||||
class RepoTreeView(QtGui.QTreeView):
|
||||
class RepoTreeView(standard.TreeView):
|
||||
"""Provides a filesystem-like view of a git repository."""
|
||||
def __init__(self, parent):
|
||||
QtGui.QTreeView.__init__(self, parent)
|
||||
super(RepoTreeView, self).__init__(parent)
|
||||
|
||||
self.setSortingEnabled(False)
|
||||
self.setAllColumnsShowFocus(True)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setUniformRowHeights(True)
|
||||
self.setAnimated(True)
|
||||
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
|
||||
# Observe model updates
|
||||
@@ -73,6 +67,10 @@ class RepoTreeView(QtGui.QTreeView):
|
||||
# The non-Qt cola application model
|
||||
self.connect(self, SIGNAL('expanded(QModelIndex)'), self.size_columns)
|
||||
self.connect(self, SIGNAL('collapsed(QModelIndex)'), self.size_columns)
|
||||
|
||||
# Sync selection before the key press event changes the model index
|
||||
self.connect(self, SIGNAL('indexAboutToChange()'), self.sync_selection)
|
||||
|
||||
self.action_history =\
|
||||
self._create_action('View History...',
|
||||
'View history for selected path(s).',
|
||||
@@ -145,86 +143,6 @@ class RepoTreeView(QtGui.QTreeView):
|
||||
menu.addAction(self.action_revert)
|
||||
menu.exec_(self.mapToGlobal(event.pos()))
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Override keyPressEvent to allow LeftArrow to work on non-directories.
|
||||
|
||||
When LeftArrow is pressed on a file entry or an unexpanded directory,
|
||||
then move the current index to the parent directory.
|
||||
|
||||
This simplifies navigation using the keyboard.
|
||||
For power-users, we support Vim keybindings ;-P
|
||||
|
||||
"""
|
||||
# Check whether the item is expanded before calling the base class
|
||||
# keyPressEvent otherwise we end up collapsing and changing the
|
||||
# current index in one shot, which we don't want to do.
|
||||
index = self.currentIndex()
|
||||
was_expanded = self.isExpanded(index)
|
||||
was_collapsed = not was_expanded
|
||||
|
||||
# Vim keybindings...
|
||||
# Rewrite the event before marshalling to QTreeView.event()
|
||||
key = event.key()
|
||||
|
||||
# Remap 'H' to 'Left'
|
||||
if key == QtCore.Qt.Key_H:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Left,
|
||||
event.modifiers())
|
||||
# Remap 'J' to 'Down'
|
||||
elif key == QtCore.Qt.Key_J:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Down,
|
||||
event.modifiers())
|
||||
# Remap 'K' to 'Up'
|
||||
elif key == QtCore.Qt.Key_K:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Up,
|
||||
event.modifiers())
|
||||
# Remap 'L' to 'Right'
|
||||
elif key == QtCore.Qt.Key_L:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Right,
|
||||
event.modifiers())
|
||||
|
||||
# Re-read the event key to take the remappings into account
|
||||
key = event.key()
|
||||
|
||||
# Process the keyPressEvent before changing the current index
|
||||
# otherwise the event will affect the new index set here
|
||||
# instead of the original index.
|
||||
result = QtGui.QTreeView.keyPressEvent(self, event)
|
||||
|
||||
# Sync the selection model
|
||||
self.sync_selection()
|
||||
|
||||
# Try to select the first item if the model index is invalid
|
||||
if not index.isValid():
|
||||
index = self.model().index(0, 0, QtCore.QModelIndex())
|
||||
if index.isValid():
|
||||
self.setCurrentIndex(index)
|
||||
return result
|
||||
|
||||
# Automatically select the first entry when expanding a directory
|
||||
if (key == QtCore.Qt.Key_Right and was_collapsed and
|
||||
self.isExpanded(index)):
|
||||
index = self.moveCursor(self.MoveDown, event.modifiers())
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
# Process non-root entries with valid parents only.
|
||||
if key == QtCore.Qt.Key_Left and index.parent().isValid():
|
||||
|
||||
# File entries have rowCount() == 0
|
||||
if self.item_from_index(index).rowCount() == 0:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
# Otherwise, do this for collapsed directories only
|
||||
elif was_collapsed:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
return result
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Synchronize the selection on mouse-press."""
|
||||
result = QtGui.QTreeView.mousePressEvent(self, event)
|
||||
|
||||
@@ -12,3 +12,9 @@ except ImportError:
|
||||
@staticmethod
|
||||
def new(*args):
|
||||
return md5.new()
|
||||
|
||||
@classmethod
|
||||
def md5(cls, value=''):
|
||||
obj = md5.new()
|
||||
obj.update(value)
|
||||
return obj
|
||||
|
||||
+1
-16
@@ -8,7 +8,6 @@ if __name__ == '__main__':
|
||||
src = os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
sys.path.insert(1, os.path.abspath(src))
|
||||
|
||||
from cola import qtutils
|
||||
from cola.dag.view import DAGView
|
||||
from cola.dag.model import DAG
|
||||
from cola.dag.controller import DAGController
|
||||
@@ -22,20 +21,6 @@ def git_dag(model, opts=None, args=None):
|
||||
view = DAGView(model, dag, None)
|
||||
ctl = DAGController(dag, view)
|
||||
view.show()
|
||||
view.raise_()
|
||||
if dag.ref:
|
||||
view.thread.start(QtCore.QThread.LowPriority)
|
||||
view.display()
|
||||
return ctl
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import cola
|
||||
from cola import app
|
||||
|
||||
model = cola.model()
|
||||
model.set_worktree(os.getcwd())
|
||||
model.update_status()
|
||||
|
||||
app = app.ColaApplication(sys.argv)
|
||||
ctl = git_dag(model)
|
||||
sys.exit(app.exec_())
|
||||
|
||||
+14
-7
@@ -3,16 +3,16 @@ import subprocess
|
||||
import os
|
||||
import cola
|
||||
from cola import core
|
||||
from cola import git
|
||||
from cola.git import git
|
||||
from cola import signals
|
||||
from cola import utils
|
||||
from cola.cmds import BaseCommand
|
||||
from cola.observable import Observable
|
||||
|
||||
# put summary at the end b/c it can contain
|
||||
# any number of funky characters
|
||||
logfmt = 'format:%H%x01%P%x01%d%x01%an%x01%aD%x01%s'
|
||||
git = git.instance()
|
||||
# any number of funky characters, including the separator
|
||||
logfmt = 'format:%H%x01%P%x01%d%x01%an%x01%ar%x01%ae%x01%s'
|
||||
logsep = chr(0x01)
|
||||
|
||||
archive = 'archive'
|
||||
|
||||
@@ -101,6 +101,7 @@ class Commit(object):
|
||||
'tags',
|
||||
'author',
|
||||
'authdate',
|
||||
'email',
|
||||
'generation',
|
||||
'parsed')
|
||||
def __init__(self, sha1=None, log_entry=None):
|
||||
@@ -109,6 +110,7 @@ class Commit(object):
|
||||
self.parents = []
|
||||
self.children = []
|
||||
self.tags = set()
|
||||
self.email = None
|
||||
self.author = None
|
||||
self.authdate = None
|
||||
self.parsed = False
|
||||
@@ -116,10 +118,10 @@ class Commit(object):
|
||||
if log_entry:
|
||||
self.parse(log_entry)
|
||||
|
||||
def parse(self, log_entry):
|
||||
def parse(self, log_entry, sep=logsep):
|
||||
self.sha1 = log_entry[:40]
|
||||
(parents, tags, author, authdate, summary) = \
|
||||
log_entry[41:].split(chr(0x01), 5)
|
||||
(parents, tags, author, authdate, email, summary) = \
|
||||
log_entry[41:].split(sep, 6)
|
||||
|
||||
if summary:
|
||||
self.summary = core.decode(summary)
|
||||
@@ -150,6 +152,8 @@ class Commit(object):
|
||||
self.author = core.decode(author)
|
||||
if authdate:
|
||||
self.authdate = authdate
|
||||
if email:
|
||||
self.email = core.decode(email)
|
||||
|
||||
self.parsed = True
|
||||
return self
|
||||
@@ -214,17 +218,20 @@ class RepoReader(object):
|
||||
except IndexError:
|
||||
self._idx = -1
|
||||
raise StopIteration
|
||||
|
||||
if self._proc is None:
|
||||
ref_args = utils.shell_split(self.dag.ref)
|
||||
cmd = self._cmd + ['-%d' % self.dag.count] + ref_args
|
||||
self._proc = utils.start_command(cmd)
|
||||
self._topo_list = []
|
||||
|
||||
log_entry = self._proc.stdout.readline().rstrip()
|
||||
if not log_entry:
|
||||
del self._proc
|
||||
self._cached = True
|
||||
self._proc = None
|
||||
raise StopIteration
|
||||
|
||||
sha1 = log_entry[:40]
|
||||
try:
|
||||
return self._objects[sha1]
|
||||
|
||||
+505
-315
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -1,11 +1,13 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola import gitcmds
|
||||
from cola.widgets import defs
|
||||
from cola.widgets import standard
|
||||
|
||||
|
||||
def launch(args):
|
||||
@@ -31,7 +33,8 @@ def diff_expression(parent, expr):
|
||||
|
||||
class FileDiffDialog(QtGui.QDialog):
|
||||
def __init__(self, parent, a=None, b=None, expr=None):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
super(FileDiffDialog, self).__init__(parent)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.expr = expr
|
||||
@@ -39,12 +42,9 @@ class FileDiffDialog(QtGui.QDialog):
|
||||
self.setWindowTitle('Select File(s)')
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
|
||||
self._tree = QtGui.QTreeWidget(self)
|
||||
self._tree.setAlternatingRowColors(True)
|
||||
self._tree = standard.TreeWidget(self)
|
||||
self._tree.setRootIsDecorated(False)
|
||||
self._tree.setSelectionMode(self._tree.ExtendedSelection)
|
||||
self._tree.setUniformRowHeights(True)
|
||||
self._tree.setAllColumnsShowFocus(True)
|
||||
self._tree.setHeaderHidden(True)
|
||||
|
||||
self._diff_btn = QtGui.QPushButton('Compare')
|
||||
|
||||
+4
-5
@@ -224,9 +224,8 @@ class GitConfig(observable.Observable):
|
||||
return opts
|
||||
|
||||
def get_guitool_names(self):
|
||||
cmds = []
|
||||
guitools = self.find('guitool.*.cmd')
|
||||
for name, cmd in guitools.items():
|
||||
name = name[len('guitool.'):-len('.cmd')]
|
||||
cmds.append(name)
|
||||
return cmds
|
||||
prefix = len('guitool.')
|
||||
suffix = len('.cmd')
|
||||
return sorted([name[prefix:-suffix]
|
||||
for (name, cmd) in guitools.items()])
|
||||
|
||||
+6
-16
@@ -215,22 +215,12 @@ def sha1_diff(sha1, git=git):
|
||||
return core.decode(git.diff(sha1 + '^!', **_common_diff_opts()))
|
||||
|
||||
|
||||
def diff_info(sha1, git=git, merge=True):
|
||||
|
||||
if merge:
|
||||
mergefmt = 'Merge: %P%n'
|
||||
else:
|
||||
mergefmt = ''
|
||||
|
||||
log = git.log('-1',
|
||||
'--pretty=format:'
|
||||
'commit %H%n' +
|
||||
mergefmt +
|
||||
'Author: %aN <%aE>%n'
|
||||
'Date: %aD%n'
|
||||
'%n%s%n%n%b',
|
||||
sha1)
|
||||
return core.decode(log) + '\n\n' + sha1_diff(sha1)
|
||||
def diff_info(sha1, git=git):
|
||||
log = git.log('-1', '--pretty=format:%b', sha1)
|
||||
decoded = core.decode(log).strip()
|
||||
if decoded:
|
||||
decoded += '\n\n'
|
||||
return decoded + sha1_diff(sha1)
|
||||
|
||||
|
||||
def diff_helper(commit=None,
|
||||
|
||||
+12
-7
@@ -56,7 +56,8 @@ def branch_delete():
|
||||
|
||||
def diff_revision():
|
||||
"""Diff an arbitrary revision against the worktree"""
|
||||
ref = choose_ref('Select Revision to Diff', 'Diff', pre=['HEAD^'])
|
||||
ref = choose_ref('Select Revision to Diff', 'Diff',
|
||||
default='HEAD^')
|
||||
if not ref:
|
||||
return
|
||||
difftool.diff_commits(qtutils.active_window(), ref, None)
|
||||
@@ -167,7 +168,14 @@ def export_patches():
|
||||
|
||||
def diff_expression():
|
||||
"""Diff using an arbitrary expression."""
|
||||
ref = choose_ref('Enter Diff Expression', 'Diff')
|
||||
tracked = gitcmds.tracked_branch()
|
||||
current = gitcmds.current_branch()
|
||||
if tracked and current:
|
||||
default = tracked + '..' + current
|
||||
else:
|
||||
default = 'origin/master..'
|
||||
ref = choose_ref('Enter Diff Expression', 'Diff',
|
||||
default=default)
|
||||
if not ref:
|
||||
return
|
||||
difftool.diff_expression(qtutils.active_window(), ref)
|
||||
@@ -217,12 +225,9 @@ def rebase():
|
||||
qtutils.log(status, output)
|
||||
|
||||
|
||||
def choose_ref(title, button_text, pre=None):
|
||||
provider = None
|
||||
if pre:
|
||||
provider = qt.GitRefProvider(pre=pre)
|
||||
def choose_ref(title, button_text, default=None):
|
||||
parent = qtutils.active_window()
|
||||
return qt.GitRefDialog.ref(title, button_text, parent, provider=provider)
|
||||
return qt.GitRefDialog.ref(title, button_text, parent, default=default)
|
||||
|
||||
|
||||
def review_branch():
|
||||
|
||||
+4
-3
@@ -353,17 +353,18 @@ class MainModel(Observable):
|
||||
return self.run_remote_action(self.git.fetch, remote, **opts)
|
||||
|
||||
def push(self, remote, **opts):
|
||||
return self.run_remote_action(self.git.fetch, remote, push=True, **opts)
|
||||
return self.run_remote_action(self.git.push, remote, push=True, **opts)
|
||||
|
||||
def pull(self, remote, **opts):
|
||||
return self.run_remote_action(self.git.pull, remote, push=True, **opts)
|
||||
|
||||
def create_branch(self, name, base, track=False):
|
||||
def create_branch(self, name, base, track=False, force=False):
|
||||
"""Create a branch named 'name' from revision 'base'
|
||||
|
||||
Pass track=True to create a local tracking branch.
|
||||
"""
|
||||
return self.git.branch(name, base, track=track,
|
||||
return self.git.branch(name, base,
|
||||
track=track, force=force,
|
||||
with_stderr=True,
|
||||
with_status=True)
|
||||
|
||||
|
||||
+22
-34
@@ -59,7 +59,7 @@ from cola.widgets.search import search
|
||||
|
||||
class MainView(standard.MainWindow):
|
||||
def __init__(self, model, parent):
|
||||
standard.MainWindow.__init__(self, parent)
|
||||
super(MainView, self).__init__(parent)
|
||||
# Default size; this is thrown out when save/restore is used
|
||||
self.resize(987, 610)
|
||||
self.model = model
|
||||
@@ -73,14 +73,16 @@ class MainView(standard.MainWindow):
|
||||
self.merge_message_hash = ''
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
|
||||
# Dockwidget options
|
||||
qtcompat.set_common_dock_options(self)
|
||||
|
||||
self.classic_dockable = gitcfg.instance().get('cola.classicdockable')
|
||||
|
||||
cfg = gitcfg.instance()
|
||||
self.classic_dockable = (cfg.get('cola.browserdockable') or
|
||||
cfg.get('cola.classicdockable'))
|
||||
if self.classic_dockable:
|
||||
self.classicdockwidget = create_dock('Classic', self)
|
||||
self.classicdockwidget = create_dock('Browser', self)
|
||||
self.classicwidget = classic_widget(self)
|
||||
self.classicdockwidget.setWidget(self.classicwidget)
|
||||
|
||||
@@ -118,7 +120,7 @@ class MainView(standard.MainWindow):
|
||||
SIGNAL(signals.load_previous_message))
|
||||
self.commitdockwidget.setWidget(self.commitmsgeditor)
|
||||
|
||||
# "Command Output" widget
|
||||
# "Console" widget
|
||||
logwidget = qtutils.logger()
|
||||
logwidget.setFont(diff_font())
|
||||
self.logdockwidget = create_dock('Console', self)
|
||||
@@ -253,7 +255,7 @@ class MainView(standard.MainWindow):
|
||||
'Review...', guicmds.review_branch)
|
||||
|
||||
self.menu_classic = add_action(self,
|
||||
'Cola Classic...', cola_classic)
|
||||
'Browser...', cola_classic)
|
||||
self.menu_classic.setIcon(qtutils.git_icon())
|
||||
|
||||
self.menu_dag = add_action(self,
|
||||
@@ -261,11 +263,15 @@ class MainView(standard.MainWindow):
|
||||
self.menu_dag.setIcon(qtutils.git_icon())
|
||||
|
||||
# Relayed actions
|
||||
status_tree = self.statusdockwidget.widget().tree
|
||||
self.addAction(status_tree.up)
|
||||
self.addAction(status_tree.down)
|
||||
self.addAction(status_tree.process_selection)
|
||||
self.addAction(status_tree.launch_difftool)
|
||||
if not self.classic_dockable:
|
||||
# These shortcuts conflict with those from the
|
||||
# 'Browser' widget so don't register them when
|
||||
# the browser is a dockable tool.
|
||||
status_tree = self.statusdockwidget.widget().tree
|
||||
self.addAction(status_tree.up)
|
||||
self.addAction(status_tree.down)
|
||||
self.addAction(status_tree.process_selection)
|
||||
self.addAction(status_tree.launch_difftool)
|
||||
|
||||
# Create the application menu
|
||||
self.menubar = QtGui.QMenuBar(self)
|
||||
@@ -408,7 +414,6 @@ class MainView(standard.MainWindow):
|
||||
self.connect(self.commitmsgeditor, SIGNAL('cursorPosition(int,int)'),
|
||||
self.show_cursor_position)
|
||||
self.connect(self, SIGNAL('update'), self._update_callback)
|
||||
self.connect(self, SIGNAL('apply_state'), self.apply_state)
|
||||
self.connect(self, SIGNAL('install_config_actions'),
|
||||
self._install_config_actions)
|
||||
|
||||
@@ -417,8 +422,9 @@ class MainView(standard.MainWindow):
|
||||
self.install_config_actions()
|
||||
|
||||
# Restore saved settings
|
||||
self._gui_state_task = None
|
||||
self._load_gui_state()
|
||||
qtutils.apply_state(self)
|
||||
|
||||
self.statusdockwidget.widget().setFocus()
|
||||
|
||||
log(0, version.git_version_str() + '\ncola version ' + version.version())
|
||||
|
||||
@@ -503,32 +509,14 @@ class MainView(standard.MainWindow):
|
||||
def apply_state(self, state):
|
||||
"""Imports data for save/restore"""
|
||||
# 1 is the widget version; change when widgets are added/removed
|
||||
standard.MainWindow.apply_state(self, state)
|
||||
super(MainView, self).apply_state(state)
|
||||
qtutils.apply_window_state(self, state, 1)
|
||||
|
||||
def export_state(self):
|
||||
"""Exports data for save/restore"""
|
||||
state = standard.MainWindow.export_state(self)
|
||||
state = super(MainView, self).export_state()
|
||||
return qtutils.export_window_state(self, state, self.widget_version)
|
||||
|
||||
def _load_gui_state(self):
|
||||
"""Restores the gui from the preferences file."""
|
||||
self._gui_state_task = self._start_gui_state_loading_thread()
|
||||
|
||||
def _start_gui_state_loading_thread(self):
|
||||
"""Do expensive file reading and json decoding in the background"""
|
||||
class LoadGUIStateTask(QtCore.QRunnable):
|
||||
def __init__(self, sender):
|
||||
QtCore.QRunnable.__init__(self)
|
||||
self._sender = sender
|
||||
def run(self):
|
||||
state = settings.Settings().get_gui_state(self._sender)
|
||||
self._sender.emit(SIGNAL('apply_state'), state)
|
||||
|
||||
task = LoadGUIStateTask(self)
|
||||
QtCore.QThreadPool.globalInstance().start(task)
|
||||
return task
|
||||
|
||||
def setup_dockwidget_tools_menu(self):
|
||||
# Hotkeys for toggling the dock widgets
|
||||
dockwidgets = (
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
from cola import qtutils
|
||||
from cola.qt import GitRefLineEdit
|
||||
from cola.qtutils import tr
|
||||
from cola.widgets import completion
|
||||
from cola.widgets import defs
|
||||
from cola.merge.model import merge, visualize_revision
|
||||
|
||||
@@ -22,7 +22,7 @@ class MergeView(QtGui.QDialog):
|
||||
self.revision_label = QtGui.QLabel()
|
||||
self.revision_label.setText(tr('Revision To Merge'))
|
||||
|
||||
self.revision = GitRefLineEdit()
|
||||
self.revision = completion.GitRefLineEdit()
|
||||
|
||||
self.radio_local = QtGui.QRadioButton()
|
||||
self.radio_local.setText(tr('Local Branch'))
|
||||
|
||||
+14
-399
@@ -1,5 +1,4 @@
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
@@ -10,11 +9,10 @@ from PyQt4.QtGui import QSyntaxHighlighter
|
||||
from PyQt4.QtGui import QTextCharFormat
|
||||
from PyQt4.QtGui import QColor
|
||||
|
||||
import cola
|
||||
from cola import utils
|
||||
from cola import qtutils
|
||||
from cola.compat import set
|
||||
from cola.qtutils import tr
|
||||
from cola.widgets import completion
|
||||
from cola.widgets import defs
|
||||
|
||||
|
||||
@@ -161,7 +159,7 @@ class ExpandableGroupBox(QtGui.QGroupBox):
|
||||
self.emit(SIGNAL('expanded(bool)'), expanded)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
if event.button() == Qt.LeftButton:
|
||||
option = QtGui.QStyleOptionGroupBox()
|
||||
self.initStyleOption(option)
|
||||
icon_size = self.arrow_icon_size
|
||||
@@ -174,7 +172,7 @@ class ExpandableGroupBox(QtGui.QGroupBox):
|
||||
QtGui.QGroupBox.mousePressEvent(self, event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if (event.button() == QtCore.Qt.LeftButton and
|
||||
if (event.button() == Qt.LeftButton and
|
||||
self.click_pos == event.pos()):
|
||||
self.set_expanded(not self.expanded)
|
||||
QtGui.QGroupBox.mouseReleaseEvent(self, event)
|
||||
@@ -185,7 +183,7 @@ class ExpandableGroupBox(QtGui.QGroupBox):
|
||||
self.initStyleOption(option)
|
||||
painter.save()
|
||||
painter.translate(self.arrow_icon_size + defs.spacing, 0)
|
||||
painter.drawText(option.rect, QtCore.Qt.AlignLeft, self.title())
|
||||
painter.drawText(option.rect, Qt.AlignLeft, self.title())
|
||||
painter.restore()
|
||||
|
||||
style = QtGui.QStyle
|
||||
@@ -198,55 +196,15 @@ class ExpandableGroupBox(QtGui.QGroupBox):
|
||||
painter.drawPrimitive(style.PE_IndicatorArrowRight, option)
|
||||
|
||||
|
||||
class GitRefCompleter(QtGui.QCompleter):
|
||||
"""Provides completion for branches and tags"""
|
||||
def __init__(self, parent, provider=None):
|
||||
QtGui.QCompleter.__init__(self, parent)
|
||||
self._model = GitRefModel(parent, provider=provider)
|
||||
self.setModel(self._model)
|
||||
self.setCompletionMode(self.UnfilteredPopupCompletion)
|
||||
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
def __del__(self):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self):
|
||||
self._model.dispose()
|
||||
|
||||
|
||||
class GitRefLineEdit(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None, provider=None):
|
||||
QtGui.QLineEdit.__init__(self, parent)
|
||||
self.refcompleter = GitRefCompleter(self, provider=provider)
|
||||
self.setCompleter(self.refcompleter)
|
||||
self.refcompleter.popup().installEventFilter(self)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Fix an annoyance on OS X
|
||||
|
||||
The completer popup steals focus. Work around it.
|
||||
This affects dialogs without QtCore.Qt.WindowModal modality.
|
||||
|
||||
"""
|
||||
if obj == self.refcompleter.popup():
|
||||
if event.type() == QtCore.QEvent.FocusIn:
|
||||
return True
|
||||
return False
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
super(GitRefLineEdit, self).mouseReleaseEvent(event)
|
||||
self.refcompleter.complete()
|
||||
|
||||
|
||||
class GitRefDialog(QtGui.QDialog):
|
||||
def __init__(self, title, button_text, parent, provider=None):
|
||||
def __init__(self, title, button_text, parent):
|
||||
super(GitRefDialog, self).__init__(parent)
|
||||
self.setWindowTitle(title)
|
||||
|
||||
self.label = QtGui.QLabel()
|
||||
self.label.setText(title)
|
||||
|
||||
self.lineedit = GitRefLineEdit(self, provider=provider)
|
||||
self.lineedit = completion.GitRefLineEdit(self)
|
||||
self.setFocusProxy(self.lineedit)
|
||||
|
||||
self.ok_button = QtGui.QPushButton()
|
||||
@@ -278,7 +236,7 @@ class GitRefDialog(QtGui.QDialog):
|
||||
self.connect(self.lineedit, SIGNAL('textChanged(QString)'),
|
||||
self.text_changed)
|
||||
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
self.ok_button.setEnabled(False)
|
||||
|
||||
def text(self):
|
||||
@@ -287,9 +245,14 @@ class GitRefDialog(QtGui.QDialog):
|
||||
def text_changed(self, txt):
|
||||
self.ok_button.setEnabled(bool(self.text()))
|
||||
|
||||
def set_text(self, ref):
|
||||
self.lineedit.setText(ref)
|
||||
|
||||
@staticmethod
|
||||
def ref(title, button_text, parent, provider=None):
|
||||
dlg = GitRefDialog(title, button_text, parent, provider=provider)
|
||||
def ref(title, button_text, parent, default=None):
|
||||
dlg = GitRefDialog(title, button_text, parent)
|
||||
if default:
|
||||
dlg.set_text(default)
|
||||
dlg.show()
|
||||
dlg.raise_()
|
||||
dlg.setFocus()
|
||||
@@ -298,354 +261,6 @@ class GitRefDialog(QtGui.QDialog):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class GitRefProvider(QtCore.QObject):
|
||||
def __init__(self, pre=None):
|
||||
super(GitRefProvider, self).__init__()
|
||||
if pre:
|
||||
self.pre = pre
|
||||
else:
|
||||
self.pre = []
|
||||
self.model = model = cola.model()
|
||||
msg = model.message_updated
|
||||
model.add_observer(msg, self.emit_updated)
|
||||
|
||||
def emit_updated(self):
|
||||
self.emit(SIGNAL('updated()'))
|
||||
|
||||
def matches(self):
|
||||
model = self.model
|
||||
return self.pre + model.local_branches + model.remote_branches + model.tags
|
||||
|
||||
def dispose(self):
|
||||
self.model.remove_observer(self.emit_updated)
|
||||
|
||||
|
||||
class GitRefModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, parent, provider=None):
|
||||
QtGui.QStandardItemModel.__init__(self, parent)
|
||||
|
||||
if provider is None:
|
||||
provider = GitRefProvider()
|
||||
self.provider = provider
|
||||
self.update_matches()
|
||||
|
||||
self.connect(self.provider, SIGNAL('updated()'),
|
||||
self.update_matches)
|
||||
|
||||
def dispose(self):
|
||||
self.provider.dispose()
|
||||
|
||||
def update_matches(self):
|
||||
QStandardItem = QtGui.QStandardItem
|
||||
self.clear()
|
||||
for match in self.provider.matches():
|
||||
item = QStandardItem()
|
||||
item.setIcon(qtutils.git_icon())
|
||||
item.setText(match)
|
||||
self.appendRow(item)
|
||||
|
||||
|
||||
class UpdateGitLogCompletionModelThread(QtCore.QThread):
|
||||
def __init__(self, model):
|
||||
QtCore.QThread.__init__(self)
|
||||
self.model = model
|
||||
self.case_insensitive = False
|
||||
|
||||
def run(self):
|
||||
text = None
|
||||
# Loop when the matched text changes between the start and end time.
|
||||
# This happens when gather_matches() takes too long and the
|
||||
# model's matched_text changes in-between.
|
||||
while text != self.model.matched_text:
|
||||
text = self.model.matched_text
|
||||
items = self.model.gather_matches(self.case_insensitive)
|
||||
self.emit(SIGNAL('items_gathered'), *items)
|
||||
|
||||
|
||||
class GitLogCompletionModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, parent):
|
||||
self.matched_text = None
|
||||
QtGui.QStandardItemModel.__init__(self, parent)
|
||||
self.cmodel = cola.model()
|
||||
self.update_thread = UpdateGitLogCompletionModelThread(self)
|
||||
self.connect(self.update_thread, SIGNAL('items_gathered'),
|
||||
self.apply_matches)
|
||||
|
||||
def lower_cmp(self, a, b):
|
||||
return cmp(a.replace('.','').lower(), b.replace('.','').lower())
|
||||
|
||||
def update_matches(self, case_insensitive):
|
||||
self.update_thread.case_insensitive = case_insensitive
|
||||
if not self.update_thread.isRunning():
|
||||
self.update_thread.start()
|
||||
|
||||
def gather_matches(self, case_sensitive):
|
||||
file_list = self.cmodel.everything()
|
||||
files = set(file_list)
|
||||
files_and_dirs = utils.add_parents(set(files))
|
||||
|
||||
dirs = files_and_dirs.difference(files)
|
||||
|
||||
model = self.cmodel
|
||||
refs = model.local_branches + model.remote_branches + model.tags
|
||||
matched_text = self.matched_text
|
||||
|
||||
if matched_text:
|
||||
if case_sensitive:
|
||||
matched_refs = [r for r in refs if matched_text in r]
|
||||
else:
|
||||
matched_refs = [r for r in refs
|
||||
if matched_text.lower() in r.lower()]
|
||||
else:
|
||||
matched_refs = refs
|
||||
|
||||
matched_refs.sort(cmp=self.lower_cmp)
|
||||
|
||||
if matched_text:
|
||||
if case_sensitive:
|
||||
matched_paths = [f for f in files_and_dirs
|
||||
if matched_text in f]
|
||||
else:
|
||||
matched_paths = [f for f in files_and_dirs
|
||||
if matched_text.lower() in f.lower()]
|
||||
else:
|
||||
matched_paths = list(files_and_dirs)
|
||||
|
||||
matched_paths.sort(cmp=self.lower_cmp)
|
||||
|
||||
return (matched_refs, matched_paths, dirs)
|
||||
|
||||
|
||||
def apply_matches(self, matched_refs, matched_paths, dirs):
|
||||
QStandardItem = QtGui.QStandardItem
|
||||
file_icon = qtutils.file_icon()
|
||||
dir_icon = qtutils.dir_icon()
|
||||
git_icon = qtutils.git_icon()
|
||||
|
||||
matched_text = self.matched_text
|
||||
items = []
|
||||
for ref in matched_refs:
|
||||
item = QStandardItem()
|
||||
item.setText(ref)
|
||||
item.setIcon(git_icon)
|
||||
items.append(item)
|
||||
|
||||
if matched_paths and (not matched_text or matched_text in '--'):
|
||||
item = QStandardItem()
|
||||
item.setText('--')
|
||||
item.setIcon(file_icon)
|
||||
items.append(item)
|
||||
|
||||
for match in matched_paths:
|
||||
item = QStandardItem()
|
||||
item.setText(match)
|
||||
if match in dirs:
|
||||
item.setIcon(dir_icon)
|
||||
else:
|
||||
item.setIcon(file_icon)
|
||||
items.append(item)
|
||||
|
||||
self.clear()
|
||||
self.invisibleRootItem().appendRows(items)
|
||||
|
||||
def set_match_text(self, text, case_sensitive):
|
||||
self.matched_text = text
|
||||
self.update_matches(case_sensitive)
|
||||
|
||||
|
||||
class GitLogLineEdit(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QLineEdit.__init__(self, parent)
|
||||
# used to hide the completion popup after a drag-select
|
||||
self._drag = 0
|
||||
|
||||
self._model = GitLogCompletionModel(self)
|
||||
self._delegate = HighlightCompletionDelegate(self)
|
||||
|
||||
self._completer = QtGui.QCompleter(self)
|
||||
self._completer.setWidget(self)
|
||||
self._completer.setModel(self._model)
|
||||
self._completer.setCompletionMode(
|
||||
QtGui.QCompleter.UnfilteredPopupCompletion)
|
||||
self._completer.popup().setItemDelegate(self._delegate)
|
||||
|
||||
self.connect(self._completer, SIGNAL('activated(QString)'),
|
||||
self._complete)
|
||||
self.connect(self, SIGNAL('textChanged(QString)'), self._text_changed)
|
||||
self._keys_to_ignore = set([QtCore.Qt.Key_Enter,
|
||||
QtCore.Qt.Key_Return,
|
||||
QtCore.Qt.Key_Escape])
|
||||
|
||||
def is_case_sensitive(self, text):
|
||||
return bool([char for char in text if char.isupper()])
|
||||
|
||||
def _text_changed(self, text):
|
||||
text = self.last_word()
|
||||
case_sensitive = self.is_case_sensitive(text)
|
||||
if case_sensitive:
|
||||
self._completer.setCaseSensitivity(QtCore.Qt.CaseSensitive)
|
||||
else:
|
||||
self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
self._delegate.set_highlight_text(text, case_sensitive)
|
||||
self._model.set_match_text(text, case_sensitive)
|
||||
|
||||
def update_matches(self):
|
||||
text = self.last_word()
|
||||
case_sensitive = self.is_case_sensitive(text)
|
||||
self._model.update_matches(case_sensitive)
|
||||
|
||||
def _complete(self, completion):
|
||||
"""
|
||||
This is the event handler for the QCompleter.activated(QString) signal,
|
||||
it is called when the user selects an item in the completer popup.
|
||||
"""
|
||||
if not completion:
|
||||
return
|
||||
words = self.words()
|
||||
if words:
|
||||
words.pop()
|
||||
words.append(unicode(completion))
|
||||
self.setText(subprocess.list2cmdline(words))
|
||||
self.emit(SIGNAL('ref_changed'))
|
||||
|
||||
def words(self):
|
||||
return utils.shell_usplit(unicode(self.text()))
|
||||
|
||||
def last_word(self):
|
||||
words = self.words()
|
||||
if not words:
|
||||
return unicode(self.text())
|
||||
if not words[-1]:
|
||||
return u''
|
||||
return words[-1]
|
||||
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.KeyPress:
|
||||
if (event.key() == QtCore.Qt.Key_Tab and
|
||||
self._completer.popup().isVisible()):
|
||||
event.ignore()
|
||||
return True
|
||||
return QtGui.QLineEdit.event(self, event)
|
||||
|
||||
def do_completion(self):
|
||||
self._completer.popup().setCurrentIndex(
|
||||
self._completer.completionModel().index(0,0))
|
||||
self._completer.complete()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if self._completer.popup().isVisible():
|
||||
if event.key() in self._keys_to_ignore:
|
||||
event.ignore()
|
||||
self._complete(self.last_word())
|
||||
return
|
||||
|
||||
elif (event.key() == QtCore.Qt.Key_Down and
|
||||
self._completer.completionCount() > 0):
|
||||
event.accept()
|
||||
self.do_completion()
|
||||
return
|
||||
|
||||
QtGui.QLineEdit.keyPressEvent(self, event)
|
||||
|
||||
prefix = self.last_word()
|
||||
if prefix != unicode(self._completer.completionPrefix()):
|
||||
self._update_popup_items(prefix)
|
||||
if len(event.text()) > 0 and len(prefix) > 0:
|
||||
self._completer.complete()
|
||||
if len(prefix) == 0:
|
||||
self._completer.popup().hide()
|
||||
|
||||
#: _drag: 0 - unclicked, 1 - clicked, 2 - dragged
|
||||
def mousePressEvent(self, event):
|
||||
self._drag = 1
|
||||
return QtGui.QLineEdit.mousePressEvent(self, event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self._drag == 1:
|
||||
self._drag = 2
|
||||
return QtGui.QLineEdit.mouseMoveEvent(self, event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self._drag != 2 and event.buttons() != QtCore.Qt.RightButton:
|
||||
self.do_completion()
|
||||
self._drag = 0
|
||||
return QtGui.QLineEdit.mouseReleaseEvent(self, event)
|
||||
|
||||
def close_popup(self):
|
||||
self._completer.popup().close()
|
||||
|
||||
def _update_popup_items(self, prefix):
|
||||
"""
|
||||
Filters the completer's popup items to only show items
|
||||
with the given prefix.
|
||||
"""
|
||||
self._completer.setCompletionPrefix(prefix)
|
||||
self._completer.popup().setCurrentIndex(
|
||||
self._completer.completionModel().index(0,0))
|
||||
|
||||
|
||||
class HighlightCompletionDelegate(QtGui.QStyledItemDelegate):
|
||||
"""A delegate used for auto-completion to give formatted completion"""
|
||||
def __init__(self, parent=None): # model, parent=None):
|
||||
QtGui.QStyledItemDelegate.__init__(self, parent)
|
||||
self.highlight_text = ''
|
||||
self.case_sensitive = False
|
||||
|
||||
self.doc = QtGui.QTextDocument()
|
||||
self.doc.setDocumentMargin(0)
|
||||
|
||||
def set_highlight_text(self, text, case_sensitive):
|
||||
"""Sets the text that will be made bold in the term name when displayed"""
|
||||
self.highlight_text = text
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
"""Overloaded Qt method for custom painting of a model index"""
|
||||
if not self.highlight_text:
|
||||
return QtGui.QStyledItemDelegate.paint(self, painter, option, index)
|
||||
|
||||
text = unicode(index.data().toPyObject())
|
||||
if self.case_sensitive:
|
||||
html = text.replace(self.highlight_text,
|
||||
'<strong>%s</strong>' % self.highlight_text)
|
||||
else:
|
||||
match = re.match('(.*)(' + self.highlight_text + ')(.*)',
|
||||
text, re.IGNORECASE)
|
||||
if match:
|
||||
start = match.group(1) or ''
|
||||
middle = match.group(2) or ''
|
||||
end = match.group(3) or ''
|
||||
html = (start + ('<strong>%s</strong>' % middle) + end)
|
||||
else:
|
||||
html = text
|
||||
self.doc.setHtml(html)
|
||||
|
||||
# Painting item without text, Text Document will paint the text
|
||||
optionV4 = QtGui.QStyleOptionViewItemV4(option)
|
||||
self.initStyleOption(optionV4, index)
|
||||
optionV4.text = QtCore.QString()
|
||||
|
||||
style = QtGui.QApplication.style()
|
||||
style.drawControl(QtGui.QStyle.CE_ItemViewItem, optionV4, painter)
|
||||
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
|
||||
|
||||
# Highlighting text if item is selected
|
||||
if (optionV4.state & QtGui.QStyle.State_Selected):
|
||||
ctx.palette.setColor(QtGui.QPalette.Text, optionV4.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
|
||||
|
||||
# translate the painter to where the text is drawn
|
||||
textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, optionV4)
|
||||
painter.save()
|
||||
|
||||
start = textRect.topLeft() + QtCore.QPoint(3, 0)
|
||||
painter.translate(start)
|
||||
painter.setClipRect(textRect.translated(-start))
|
||||
|
||||
# tell the text document to draw the html for us
|
||||
self.doc.documentLayout().draw(painter, ctx)
|
||||
painter.restore()
|
||||
|
||||
# Syntax highlighting
|
||||
|
||||
def TERMINAL(pattern):
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
from cola import qt
|
||||
@@ -15,7 +16,8 @@ from cola.widgets.diff import DiffTextEdit
|
||||
|
||||
class StashView(standard.Dialog):
|
||||
def __init__(self, model, parent=None):
|
||||
standard.Dialog.__init__(self, parent=parent)
|
||||
super(StashView, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.model = model
|
||||
self.stashes = []
|
||||
self.revids = []
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"""Provide cola's version number"""
|
||||
|
||||
# The current git-cola version
|
||||
_default_version = '1.7.3'
|
||||
_default_version = '1.7.5'
|
||||
|
||||
|
||||
import os
|
||||
|
||||
+44
-34
@@ -1,26 +1,23 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
from PyQt4.QtCore import Qt
|
||||
|
||||
|
||||
from cola import resources
|
||||
from cola import qtutils
|
||||
from cola import utils
|
||||
from cola import version
|
||||
from cola.prefs import diff_font
|
||||
from cola.widgets import defs
|
||||
|
||||
def launch_about_dialog():
|
||||
"""Launches the Help -> About dialog"""
|
||||
view = AboutView(qtutils.active_window())
|
||||
view.show()
|
||||
view.set_version(version.version())
|
||||
view.show()
|
||||
|
||||
|
||||
COPYRIGHT = """git-cola: The highly caffeinated git GUI v$VERSION
|
||||
|
||||
git-cola is a sweet, carbonated git GUI known for its
|
||||
sugary flavor and caffeine-inspired features.
|
||||
|
||||
|
||||
Copyright (C) 2007-2012, David Aguilar and contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or
|
||||
@@ -47,39 +44,52 @@ class AboutView(QtGui.QDialog):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
|
||||
self.setWindowTitle('About git-cola')
|
||||
self.setStyleSheet('QWidget { background-color: white; color: #666; }')
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
|
||||
self.setMinimumSize(QtCore.QSize(280, 420))
|
||||
self.setMaximumSize(QtCore.QSize(280, 420))
|
||||
self.label = QtGui.QLabel()
|
||||
self.pixmap = QtGui.QPixmap('icons:logo-top.png')
|
||||
#self.label.setStyleSheet('QWidget {background: #000; }')
|
||||
self.label.setPixmap(self.pixmap)
|
||||
self.label.setAlignment(Qt.AlignRight | Qt.AlignTop)
|
||||
|
||||
self.ham = QtGui.QLabel(self)
|
||||
self.ham.setGeometry(QtCore.QRect(0, -10, 291, 121))
|
||||
self.ham.setPixmap(QtGui.QPixmap('images:logo-cola.png'))
|
||||
palette = self.label.palette()
|
||||
palette.setColor(QtGui.QPalette.Window, Qt.black)
|
||||
self.label.setAutoFillBackground(True)
|
||||
self.label.setPalette(palette)
|
||||
|
||||
self.spam = QtGui.QLabel(self)
|
||||
self.spam.setGeometry(QtCore.QRect(10, 110, 261, 261))
|
||||
self.text = QtGui.QTextEdit()
|
||||
self.text.setReadOnly(True)
|
||||
self.text.setLineWrapMode(QtGui.QTextEdit.NoWrap)
|
||||
self.text.setAcceptRichText(False)
|
||||
self.text.setTextInteractionFlags(Qt.TextSelectableByKeyboard |
|
||||
Qt.TextSelectableByMouse)
|
||||
self.text.setFont(diff_font())
|
||||
self.text.setPlainText(COPYRIGHT)
|
||||
|
||||
font = QtGui.QFont()
|
||||
font.setFamily('Sans Serif')
|
||||
font.setPointSize(5)
|
||||
self.spam.setFont(font)
|
||||
self.close_button = QtGui.QPushButton()
|
||||
self.close_button.setText(self.tr('Close'))
|
||||
self.close_button.setDefault(True)
|
||||
|
||||
self.spam.setTextFormat(QtCore.Qt.LogText)
|
||||
self.spam.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
||||
self.spam.setWordWrap(True)
|
||||
self.spam.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
|
||||
self.spam.setText(COPYRIGHT)
|
||||
self.button_layout = QtGui.QHBoxLayout()
|
||||
self.button_layout.addStretch()
|
||||
self.button_layout.addWidget(self.close_button)
|
||||
|
||||
self.kthx = QtGui.QPushButton(self)
|
||||
self.kthx.setGeometry(QtCore.QRect(60, 380, 160, 24))
|
||||
self.kthx.setText('kthx bye')
|
||||
self.kthx.setDefault(True)
|
||||
self.main_layout = QtGui.QVBoxLayout()
|
||||
self.main_layout.setMargin(0)
|
||||
self.main_layout.setSpacing(defs.spacing)
|
||||
|
||||
self.connect(self.kthx, SIGNAL('clicked()'), self.accept)
|
||||
self.main_layout.addWidget(self.label)
|
||||
self.main_layout.addWidget(self.text)
|
||||
self.main_layout.addLayout(self.button_layout)
|
||||
self.setLayout(self.main_layout)
|
||||
|
||||
self.resize(666, 420)
|
||||
|
||||
qtutils.connect_button(self.close_button, self.accept)
|
||||
|
||||
def set_version(self, version):
|
||||
"""Sets the version field in the 'about' dialog"""
|
||||
self.spam.setText(self.spam.text().replace('$VERSION', version))
|
||||
self.text.setPlainText(self.text.toPlainText().replace('$VERSION', version))
|
||||
|
||||
|
||||
def show_shortcuts():
|
||||
@@ -103,7 +113,7 @@ def show_shortcuts():
|
||||
except AttributeError:
|
||||
parent = qtutils.active_window()
|
||||
widget = show_shortcuts.widget = QtGui.QDialog(parent)
|
||||
widget.setWindowModality(QtCore.Qt.WindowModal)
|
||||
widget.setWindowModality(Qt.WindowModal)
|
||||
|
||||
web = QtWebKit.QWebView(parent)
|
||||
web.setHtml(html)
|
||||
@@ -117,8 +127,8 @@ def show_shortcuts():
|
||||
widget.resize(800, min(parent.height(), 600))
|
||||
|
||||
qtutils.add_action(widget, 'Close', widget.accept,
|
||||
QtCore.Qt.Key_Question,
|
||||
QtCore.Qt.Key_Enter,
|
||||
QtCore.Qt.Key_Return)
|
||||
Qt.Key_Question,
|
||||
Qt.Key_Enter,
|
||||
Qt.Key_Return)
|
||||
widget.show()
|
||||
return widget
|
||||
|
||||
+7
-83
@@ -1,5 +1,6 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
import cola
|
||||
@@ -12,6 +13,7 @@ from cola.cmds import BaseCommand
|
||||
from cola.ctrl import Controller
|
||||
from cola.git import git
|
||||
from cola.widgets import defs
|
||||
from cola.widgets import standard
|
||||
|
||||
|
||||
save_blob = 'save_blob'
|
||||
@@ -111,8 +113,9 @@ class BrowseDialog(QtGui.QDialog):
|
||||
return model.filename
|
||||
|
||||
def __init__(self, model, select_file=False, parent=None):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
super(BrowseDialog, self).__init__(parent)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
|
||||
# updated for use by commands
|
||||
self.model = model
|
||||
@@ -188,13 +191,10 @@ class BrowseDialog(QtGui.QDialog):
|
||||
self.save.setEnabled(bool(filenames))
|
||||
|
||||
|
||||
class GitTreeWidget(QtGui.QTreeView):
|
||||
class GitTreeWidget(standard.TreeView):
|
||||
def __init__(self, ref, parent=None):
|
||||
QtGui.QTreeView.__init__(self, parent)
|
||||
super(GitTreeWidget, self).__init__(parent)
|
||||
self.setHeaderHidden(True)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setAllColumnsShowFocus(True)
|
||||
self.setUniformRowHeights(True)
|
||||
|
||||
self.connect(self, SIGNAL('doubleClicked(const QModelIndex &)'),
|
||||
self.double_clicked)
|
||||
@@ -215,82 +215,6 @@ class GitTreeWidget(QtGui.QTreeView):
|
||||
QtGui.QTreeView.selectionChanged(self, old_selection, new_selection)
|
||||
self.emit(SIGNAL('selectionChanged()'))
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Override keyPressEvent to allow LeftArrow to work on non-directories.
|
||||
|
||||
When LeftArrow is pressed on a file entry or an unexpanded directory,
|
||||
then move the current index to the parent directory.
|
||||
|
||||
This simplifies navigation using the keyboard.
|
||||
For power-users, we support Vim keybindings ;-P
|
||||
|
||||
"""
|
||||
# Check whether the item is expanded before calling the base class
|
||||
# keyPressEvent otherwise we end up collapsing and changing the
|
||||
# current index in one shot, which we don't want to do.
|
||||
index = self.currentIndex()
|
||||
was_expanded = self.isExpanded(index)
|
||||
was_collapsed = not was_expanded
|
||||
|
||||
# Vim keybindings...
|
||||
# Rewrite the event before marshalling to QTreeView.event()
|
||||
key = event.key()
|
||||
|
||||
# Remap 'H' to 'Left'
|
||||
if key == QtCore.Qt.Key_H:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Left,
|
||||
event.modifiers())
|
||||
# Remap 'J' to 'Down'
|
||||
elif key == QtCore.Qt.Key_J:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Down,
|
||||
event.modifiers())
|
||||
# Remap 'K' to 'Up'
|
||||
elif key == QtCore.Qt.Key_K:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Up,
|
||||
event.modifiers())
|
||||
# Remap 'L' to 'Right'
|
||||
elif key == QtCore.Qt.Key_L:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
QtCore.Qt.Key_Right,
|
||||
event.modifiers())
|
||||
|
||||
# Re-read the event key to take the remappings into account
|
||||
key = event.key()
|
||||
|
||||
# Process the keyPressEvent before changing the current index
|
||||
# otherwise the event will affect the new index set here
|
||||
# instead of the original index.
|
||||
result = QtGui.QTreeView.keyPressEvent(self, event)
|
||||
|
||||
# Try to select the first item if the model index is invalid
|
||||
if not index.isValid():
|
||||
index = self.model().index(0, 0, QtCore.QModelIndex())
|
||||
if index.isValid():
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
# Automatically select the first entry when expanding a directory
|
||||
elif (key == QtCore.Qt.Key_Right and was_collapsed and
|
||||
self.isExpanded(index)):
|
||||
index = self.moveCursor(self.MoveDown, event.modifiers())
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
# Process non-root entries with valid parents only.
|
||||
elif key == QtCore.Qt.Key_Left and index.parent().isValid():
|
||||
|
||||
# File entries have rowCount() == 0
|
||||
if self.model().itemFromIndex(index).rowCount() == 0:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
# Otherwise, do this for collapsed directories only
|
||||
elif was_collapsed:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
return result
|
||||
|
||||
def select_first_file(self):
|
||||
"""Select the first filename in the tree"""
|
||||
model = self.model()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
import cola
|
||||
@@ -10,8 +11,8 @@ from cola import gitcmds
|
||||
from cola import qt
|
||||
from cola import qtutils
|
||||
from cola import signals
|
||||
from cola.qt import GitRefLineEdit
|
||||
from cola.widgets import defs
|
||||
from cola.widgets import completion
|
||||
from cola.widgets import standard
|
||||
|
||||
|
||||
@@ -22,16 +23,13 @@ def install_command_wrapper():
|
||||
|
||||
def get_config_actions():
|
||||
cfg = gitcfg.instance()
|
||||
names = cfg.get_guitool_names()
|
||||
return names or []
|
||||
return cfg.get_guitool_names()
|
||||
|
||||
|
||||
def run_command(title, command):
|
||||
"""Show a command widget"""
|
||||
view = GitCommandWidget(qtutils.active_window())
|
||||
view.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
view = GitCommandWidget(title, qtutils.active_window())
|
||||
view.set_command(command)
|
||||
view.setWindowTitle(title)
|
||||
view.show()
|
||||
view.raise_()
|
||||
view.run()
|
||||
@@ -42,10 +40,13 @@ def run_command(title, command):
|
||||
class GitCommandWidget(standard.Dialog):
|
||||
"""Nice TextView that reads the output of a command syncronously"""
|
||||
# Keep us in scope otherwise PyQt kills the widget
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, title, parent=None):
|
||||
standard.Dialog.__init__(self, parent)
|
||||
self.resize(720, 420)
|
||||
|
||||
self.setWindowTitle(title)
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
|
||||
# Construct the process
|
||||
self.proc = QtCore.QProcess(self)
|
||||
self.exitstatus = 0
|
||||
@@ -180,10 +181,11 @@ class ActionCommandWrapper(object):
|
||||
class ActionDialog(standard.Dialog):
|
||||
def __init__(self, parent, name, opts):
|
||||
standard.Dialog.__init__(self, parent)
|
||||
self.setWindowModality(QtCore.Qt.ApplicationModal)
|
||||
self.name = name
|
||||
self.opts = opts
|
||||
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
|
||||
self.layt = QtGui.QVBoxLayout()
|
||||
self.layt.setMargin(defs.margin)
|
||||
self.layt.setSpacing(defs.spacing)
|
||||
@@ -276,7 +278,7 @@ class RevisionSelector(QtGui.QWidget):
|
||||
self._rev_label = QtGui.QLabel()
|
||||
self._rev_layt.addWidget(self._rev_label)
|
||||
|
||||
self._revision = GitRefLineEdit()
|
||||
self._revision = completion.GitRefLineEdit()
|
||||
self._rev_layt.addWidget(self._revision)
|
||||
|
||||
self._layt.addLayout(self._rev_layt)
|
||||
|
||||
+96
-134
@@ -1,5 +1,6 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
import cola
|
||||
@@ -22,6 +23,8 @@ from cola.prefs import diff_font
|
||||
from cola.dag.model import DAG
|
||||
from cola.dag.model import RepoReader
|
||||
from cola.widgets.selectcommits import select_commits
|
||||
from cola.widgets.text import HintedLineEdit
|
||||
from cola.widgets.text import HintedTextEdit
|
||||
|
||||
|
||||
class CommitMessageEditor(QtGui.QWidget):
|
||||
@@ -30,20 +33,8 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
|
||||
self.model = model
|
||||
self.notifying = False
|
||||
self.summary_placeholder = u'Commit summary'
|
||||
self.description_placeholder = u'Extended description...'
|
||||
|
||||
# Palette for normal text
|
||||
self.default_palette = QtGui.QPalette(self.palette())
|
||||
|
||||
# Palette used for the placeholder text
|
||||
self.placeholder_palette = pal = QtGui.QPalette(self.palette())
|
||||
color = pal.text().color()
|
||||
color.setAlpha(128)
|
||||
pal.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
|
||||
pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Text, color)
|
||||
|
||||
self.summary = QtGui.QLineEdit()
|
||||
self.summary = CommitSummaryLineEdit()
|
||||
self.description = CommitMessageTextEdit()
|
||||
|
||||
self.commit_button = create_toolbutton(text='Commit@@verb',
|
||||
@@ -104,10 +95,17 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
self.set_commit_message)
|
||||
|
||||
self.connect(self.summary, SIGNAL('returnPressed()'),
|
||||
self.summary_return_pressed)
|
||||
self.focus_description)
|
||||
|
||||
self.connect(self.summary, SIGNAL('cursorPositionChanged(int,int)'),
|
||||
lambda x, y: self.emit_summary_position())
|
||||
self.connect(self.summary, SIGNAL('downPressed()'),
|
||||
self.focus_description)
|
||||
|
||||
self.connect(self.summary, SIGNAL('cursorPosition(int,int)'),
|
||||
self.emit_position)
|
||||
|
||||
self.connect(self.description, SIGNAL('cursorPosition(int,int)'),
|
||||
# description starts at line 2
|
||||
lambda row, col: self.emit_position(row + 2, col))
|
||||
|
||||
# Keep model informed of changes
|
||||
self.connect(self.summary, SIGNAL('textChanged(QString)'),
|
||||
@@ -116,19 +114,13 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
self.connect(self.description, SIGNAL('textChanged()'),
|
||||
self.commit_message_changed)
|
||||
|
||||
self.connect(self.description, SIGNAL('cursorPositionChanged()'),
|
||||
self.emit_cursor_position)
|
||||
|
||||
self.connect(self.description, SIGNAL('shiftTab()'),
|
||||
self.focus_summary)
|
||||
|
||||
self.setFont(diff_font())
|
||||
|
||||
self.summary.installEventFilter(self)
|
||||
self.description.installEventFilter(self)
|
||||
|
||||
self.enable_placeholder_summary(True)
|
||||
self.enable_placeholder_description(True)
|
||||
self.summary.enable_hint(True)
|
||||
self.description.enable_hint(True)
|
||||
|
||||
self.commit_button.setEnabled(False)
|
||||
self.commit_action.setEnabled(False)
|
||||
@@ -138,86 +130,13 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
def focus_summary(self):
|
||||
self.summary.setFocus(True)
|
||||
|
||||
def is_summary_placeholder(self):
|
||||
summary = unicode(self.summary.text()).strip()
|
||||
return summary == self.summary_placeholder
|
||||
|
||||
def is_description_placeholder(self):
|
||||
description = unicode(self.description.toPlainText()).strip()
|
||||
return description == self.description_placeholder
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if obj == self.summary:
|
||||
if event.type() == QtCore.QEvent.FocusIn:
|
||||
self.emit_summary_position()
|
||||
if self.is_summary_placeholder():
|
||||
self.enable_placeholder_summary(False)
|
||||
|
||||
elif event.type() == QtCore.QEvent.FocusOut:
|
||||
if not bool(self.commit_summary()):
|
||||
self.enable_placeholder_summary(True)
|
||||
|
||||
elif obj == self.description:
|
||||
if event.type() == QtCore.QEvent.FocusIn:
|
||||
self.emit_cursor_position()
|
||||
if self.is_description_placeholder():
|
||||
self.enable_placeholder_description(False)
|
||||
|
||||
elif event.type() == QtCore.QEvent.FocusOut:
|
||||
if not bool(self.commit_description()):
|
||||
self.enable_placeholder_description(True)
|
||||
|
||||
return False
|
||||
|
||||
def enable_placeholder_summary(self, placeholder):
|
||||
blocksignals = self.summary.blockSignals(True)
|
||||
if placeholder:
|
||||
self.summary.setText(self.summary_placeholder)
|
||||
else:
|
||||
self.summary.clear()
|
||||
self.summary.setCursorPosition(0)
|
||||
self.summary.blockSignals(blocksignals)
|
||||
self.set_placeholder_palette(self.summary, placeholder)
|
||||
|
||||
def enable_placeholder_description(self, placeholder):
|
||||
blocksignals = self.description.blockSignals(True)
|
||||
if placeholder:
|
||||
self.description.setPlainText(self.description_placeholder)
|
||||
else:
|
||||
self.description.clear()
|
||||
self.description.blockSignals(blocksignals)
|
||||
self.set_placeholder_palette(self.description, placeholder)
|
||||
|
||||
def set_placeholder_palette(self, widget, placeholder):
|
||||
if placeholder:
|
||||
widget.setPalette(self.placeholder_palette)
|
||||
else:
|
||||
widget.setPalette(self.default_palette)
|
||||
|
||||
def summary_return_pressed(self):
|
||||
if bool(self.commit_summary()):
|
||||
self.description.setFocus(True)
|
||||
|
||||
def commit_summary(self):
|
||||
"""Return the commit summary as unicode"""
|
||||
summary = unicode(self.summary.text()).strip()
|
||||
if summary != self.summary_placeholder:
|
||||
return summary
|
||||
else:
|
||||
return u''
|
||||
|
||||
def commit_description(self):
|
||||
"""Return the commit description as unicode"""
|
||||
description = unicode(self.description.toPlainText()).strip()
|
||||
if description != self.description_placeholder:
|
||||
return description
|
||||
else:
|
||||
return u''
|
||||
def focus_description(self):
|
||||
self.description.setFocus(True)
|
||||
|
||||
def commit_message(self):
|
||||
"""Return the commit message as a unicode string"""
|
||||
summary = self.commit_summary()
|
||||
description = self.commit_description()
|
||||
summary = self.summary.value()
|
||||
description = self.description.value()
|
||||
if summary and description:
|
||||
return summary + u'\n\n' + description
|
||||
elif summary:
|
||||
@@ -232,21 +151,19 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
self.notifying = True
|
||||
message = self.commit_message()
|
||||
self.model.set_commitmsg(message)
|
||||
self.update_placeholder_state()
|
||||
self.refresh_palettes()
|
||||
self.notifying = False
|
||||
self.update_actions()
|
||||
|
||||
def update_actions(self):
|
||||
commit_enabled = bool(self.commit_summary())
|
||||
commit_enabled = bool(self.summary.value())
|
||||
self.commit_button.setEnabled(commit_enabled)
|
||||
self.commit_action.setEnabled(commit_enabled)
|
||||
|
||||
def update_placeholder_state(self):
|
||||
"""Update the color palette for the placeholder text"""
|
||||
self.set_placeholder_palette(self.summary,
|
||||
self.is_summary_placeholder())
|
||||
self.set_placeholder_palette(self.description,
|
||||
self.is_description_placeholder())
|
||||
def refresh_palettes(self):
|
||||
"""Update the color palette for the hint text"""
|
||||
self.summary.refresh_palette()
|
||||
self.description.refresh_palette()
|
||||
|
||||
def set_commit_message(self, message):
|
||||
"""Set the commit message to match the observed model"""
|
||||
@@ -291,7 +208,7 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
|
||||
# Update summary
|
||||
if not summary and not self.summary.hasFocus():
|
||||
summary = self.summary_placeholder
|
||||
summary = self.summary.hint()
|
||||
|
||||
blocksignals = self.summary.blockSignals(True)
|
||||
self.summary.setText(summary)
|
||||
@@ -300,24 +217,24 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
|
||||
# Update description
|
||||
if not description and not self.description.hasFocus():
|
||||
description = self.description_placeholder
|
||||
description = self.description.hint()
|
||||
|
||||
blocksignals = self.description.blockSignals(True)
|
||||
self.description.setPlainText(description)
|
||||
self.description.blockSignals(blocksignals)
|
||||
|
||||
# Update text color
|
||||
self.update_placeholder_state()
|
||||
self.refresh_palettes()
|
||||
|
||||
# Focus the empty summary or description
|
||||
if focus_summary:
|
||||
self.summary.setFocus(True)
|
||||
self.emit_summary_position()
|
||||
self.summary.emit_position()
|
||||
elif focus_description:
|
||||
self.description.setFocus(True)
|
||||
self.emit_cursor_position()
|
||||
self.description.emit_position()
|
||||
else:
|
||||
self.emit_summary_position()
|
||||
self.summary.emit_position()
|
||||
|
||||
self.update_actions()
|
||||
|
||||
@@ -332,22 +249,12 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
self.amend_action.setChecked(checked)
|
||||
self.amend_action.blockSignals(blocksignals)
|
||||
|
||||
def emit_summary_position(self):
|
||||
cols = self.summary.cursorPosition()
|
||||
self.emit(SIGNAL('cursorPosition(int,int)'), 1, cols)
|
||||
|
||||
def emit_cursor_position(self):
|
||||
"""Update the UI with the current row and column."""
|
||||
cursor = self.description.textCursor()
|
||||
position = cursor.position()
|
||||
txt = unicode(self.description.toPlainText())
|
||||
rows = txt[:position].count('\n') + 1 + 2 # description starts at 2
|
||||
cols = cursor.columnNumber()
|
||||
self.emit(SIGNAL('cursorPosition(int,int)'), rows, cols)
|
||||
def emit_position(self, row, col):
|
||||
self.emit(SIGNAL('cursorPosition(int,int)'), row, col)
|
||||
|
||||
def commit(self):
|
||||
"""Attempt to create a commit from the index and commit message."""
|
||||
if not bool(self.commit_summary()):
|
||||
if not bool(self.summary.value()):
|
||||
# Describe a good commit message
|
||||
error_msg = tr(''
|
||||
'Please supply a commit message.\n\n'
|
||||
@@ -431,15 +338,30 @@ class CommitMessageEditor(QtGui.QWidget):
|
||||
self.emit(SIGNAL(signals.load_previous_message), sha1)
|
||||
|
||||
|
||||
class CommitMessageTextEdit(QtGui.QTextEdit):
|
||||
class CommitSummaryLineEdit(HintedLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QTextEdit.__init__(self, parent)
|
||||
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
|
||||
self.setAcceptRichText(False)
|
||||
hint = u'Commit summary'
|
||||
super(CommitSummaryLineEdit, self).__init__(hint, parent)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Down:
|
||||
position = self.cursorPosition()
|
||||
curtext = unicode(self.text())
|
||||
if position == len(curtext):
|
||||
self.emit(SIGNAL('downPressed()'))
|
||||
event.ignore()
|
||||
return
|
||||
super(CommitSummaryLineEdit, self).keyPressEvent(event)
|
||||
|
||||
|
||||
class CommitMessageTextEdit(HintedTextEdit):
|
||||
def __init__(self, parent=None):
|
||||
hint = u'Extended description...'
|
||||
super(CommitMessageTextEdit, self).__init__(hint, parent)
|
||||
self.setMinimumSize(QtCore.QSize(1, 1))
|
||||
|
||||
self.action_emit_shift_tab = add_action(self,
|
||||
'Shift Tab', self.shift_tab, 'Shift+tab')
|
||||
'Shift Tab', self.emit_shift_tab, 'Shift+tab')
|
||||
|
||||
self.installEventFilter(self)
|
||||
|
||||
@@ -454,5 +376,45 @@ class CommitMessageTextEdit(QtGui.QTextEdit):
|
||||
|
||||
return False
|
||||
|
||||
def shift_tab(self):
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Up:
|
||||
cursor = self.textCursor()
|
||||
position = cursor.position()
|
||||
if position == 0:
|
||||
if cursor.hasSelection():
|
||||
cursor.setPosition(0)
|
||||
self.setTextCursor(cursor)
|
||||
else:
|
||||
self.emit_shift_tab()
|
||||
event.ignore()
|
||||
return
|
||||
text_before = unicode(self.toPlainText())[:position]
|
||||
lines_before = text_before.count('\n')
|
||||
if lines_before == 0:
|
||||
if event.modifiers() & Qt.ShiftModifier:
|
||||
mode = QtGui.QTextCursor.KeepAnchor
|
||||
else:
|
||||
mode = QtGui.QTextCursor.MoveAnchor
|
||||
cursor.setPosition(0, mode)
|
||||
self.setTextCursor(cursor)
|
||||
event.ignore()
|
||||
return
|
||||
elif event.key() == Qt.Key_Down:
|
||||
cursor = self.textCursor()
|
||||
position = cursor.position()
|
||||
all_text = unicode(self.toPlainText())
|
||||
text_after = all_text[position:]
|
||||
lines_after = text_after.count('\n')
|
||||
if lines_after == 0:
|
||||
if event.modifiers() & Qt.ShiftModifier:
|
||||
mode = QtGui.QTextCursor.KeepAnchor
|
||||
else:
|
||||
mode = QtGui.QTextCursor.MoveAnchor
|
||||
cursor.setPosition(len(all_text), mode)
|
||||
self.setTextCursor(cursor)
|
||||
event.ignore()
|
||||
return
|
||||
super(CommitMessageTextEdit, self).keyPressEvent(event)
|
||||
|
||||
def emit_shift_tab(self):
|
||||
self.emit(SIGNAL('shiftTab()'))
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
import cola
|
||||
from cola import qtutils
|
||||
from cola import utils
|
||||
from cola.compat import set
|
||||
|
||||
|
||||
class CompletionLineEdit(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(CompletionLineEdit, self).__init__(parent)
|
||||
# used to hide the completion popup after a drag-select
|
||||
self._drag = 0
|
||||
|
||||
self._completer = None
|
||||
self._delegate = HighlightDelegate(self)
|
||||
self.connect(self, SIGNAL('textChanged(QString)'), self._text_changed)
|
||||
self._keys_to_ignore = set([Qt.Key_Enter, Qt.Key_Return,
|
||||
Qt.Key_Escape])
|
||||
|
||||
def value(self):
|
||||
return unicode(self.text())
|
||||
|
||||
def setCompleter(self, completer):
|
||||
self._completer = completer
|
||||
completer.setWidget(self)
|
||||
completer.popup().setItemDelegate(self._delegate)
|
||||
self.connect(self._completer, SIGNAL('activated(QString)'),
|
||||
self._complete)
|
||||
|
||||
def _is_case_sensitive(self, text):
|
||||
return bool([char for char in text if char.isupper()])
|
||||
|
||||
def _text_changed(self, text):
|
||||
text = self._last_word()
|
||||
case_sensitive = self._is_case_sensitive(text)
|
||||
if case_sensitive:
|
||||
self._completer.setCaseSensitivity(Qt.CaseSensitive)
|
||||
else:
|
||||
self._completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self._delegate.set_highlight_text(text, case_sensitive)
|
||||
self._completer.set_match_text(text, case_sensitive)
|
||||
|
||||
def update_matches(self):
|
||||
text = self._last_word()
|
||||
case_sensitive = self._is_case_sensitive(text)
|
||||
self._completer.model().update_matches(case_sensitive)
|
||||
|
||||
def _complete(self, completion):
|
||||
"""
|
||||
This is the event handler for the QCompleter.activated(QString) signal,
|
||||
it is called when the user selects an item in the completer popup.
|
||||
"""
|
||||
if not completion:
|
||||
return
|
||||
words = self._words()
|
||||
if words and not self._ends_with_whitespace():
|
||||
words.pop()
|
||||
words.append(unicode(completion))
|
||||
self.setText(subprocess.list2cmdline(words))
|
||||
self.emit(SIGNAL('ref_changed'))
|
||||
|
||||
def _words(self):
|
||||
return utils.shell_usplit(unicode(self.text()))
|
||||
|
||||
def _ends_with_whitespace(self):
|
||||
text = unicode(self.text())
|
||||
return text.rstrip() != text
|
||||
|
||||
def _last_word(self):
|
||||
if self._ends_with_whitespace():
|
||||
return u''
|
||||
words = self._words()
|
||||
if not words:
|
||||
return unicode(self.text())
|
||||
if not words[-1]:
|
||||
return u''
|
||||
return words[-1]
|
||||
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.KeyPress:
|
||||
if (event.key() == Qt.Key_Tab and
|
||||
self._completer.popup().isVisible()):
|
||||
event.ignore()
|
||||
return True
|
||||
if event.type() == QtCore.QEvent.Hide:
|
||||
self.close_popup()
|
||||
return super(CompletionLineEdit, self).event(event)
|
||||
|
||||
def do_completion(self):
|
||||
self._completer.popup().setCurrentIndex(
|
||||
self._completer.model().index(0,0))
|
||||
self._completer.complete()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if self._completer.popup().isVisible():
|
||||
if event.key() in self._keys_to_ignore:
|
||||
event.ignore()
|
||||
self._complete(self._last_word())
|
||||
return
|
||||
|
||||
elif (event.key() == Qt.Key_Down and
|
||||
self._completer.completionCount() > 0):
|
||||
event.accept()
|
||||
self.do_completion()
|
||||
return
|
||||
|
||||
super(CompletionLineEdit, self).keyPressEvent(event)
|
||||
|
||||
prefix = self._last_word()
|
||||
if prefix != unicode(self._completer.completionPrefix()):
|
||||
self._update_popup_items(prefix)
|
||||
if len(event.text()) > 0 and len(prefix) > 0:
|
||||
self._completer.complete()
|
||||
#if len(prefix) == 0:
|
||||
# self._completer.popup().hide()
|
||||
|
||||
#: _drag: 0 - unclicked, 1 - clicked, 2 - dragged
|
||||
def mousePressEvent(self, event):
|
||||
self._drag = 1
|
||||
return super(CompletionLineEdit, self).mousePressEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self._drag == 1:
|
||||
self._drag = 2
|
||||
return super(CompletionLineEdit, self).mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self._drag != 2 and event.buttons() != Qt.RightButton:
|
||||
self.do_completion()
|
||||
self._drag = 0
|
||||
return super(CompletionLineEdit, self).mouseReleaseEvent(event)
|
||||
|
||||
def close_popup(self):
|
||||
if self._completer.popup().isVisible():
|
||||
self._completer.popup().close()
|
||||
|
||||
def _update_popup_items(self, prefix):
|
||||
"""
|
||||
Filters the completer's popup items to only show items
|
||||
with the given prefix.
|
||||
"""
|
||||
self._completer.setCompletionPrefix(prefix)
|
||||
self._completer.popup().setCurrentIndex(
|
||||
self._completer.model().index(0,0))
|
||||
|
||||
def __del__(self):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self):
|
||||
self._completer.dispose()
|
||||
|
||||
|
||||
class GatherCompletionsThread(QtCore.QThread):
|
||||
def __init__(self, model):
|
||||
QtCore.QThread.__init__(self)
|
||||
self.model = model
|
||||
self.case_sensitive = False
|
||||
|
||||
def run(self):
|
||||
text = None
|
||||
# Loop when the matched text changes between the start and end time.
|
||||
# This happens when gather_matches() takes too long and the
|
||||
# model's matched_text changes in-between.
|
||||
while text != self.model.matched_text:
|
||||
text = self.model.matched_text
|
||||
items = self.model.gather_matches(self.case_sensitive)
|
||||
|
||||
if text is not None:
|
||||
self.emit(SIGNAL('items_gathered'), items)
|
||||
|
||||
|
||||
class HighlightDelegate(QtGui.QStyledItemDelegate):
|
||||
"""A delegate used for auto-completion to give formatted completion"""
|
||||
def __init__(self, parent=None): # model, parent=None):
|
||||
super(HighlightDelegate, self).__init__(parent)
|
||||
self.highlight_text = ''
|
||||
self.case_sensitive = False
|
||||
|
||||
self.doc = QtGui.QTextDocument()
|
||||
self.doc.setDocumentMargin(0)
|
||||
|
||||
def set_highlight_text(self, text, case_sensitive):
|
||||
"""Sets the text that will be made bold in the term name when displayed"""
|
||||
self.highlight_text = text
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
"""Overloaded Qt method for custom painting of a model index"""
|
||||
if not self.highlight_text:
|
||||
return super(HighlightDelegate, self).paint(painter, option, index)
|
||||
|
||||
text = unicode(index.data().toPyObject())
|
||||
if self.case_sensitive:
|
||||
html = text.replace(self.highlight_text,
|
||||
'<strong>%s</strong>' % self.highlight_text)
|
||||
else:
|
||||
match = re.match('(.*)(' + self.highlight_text + ')(.*)',
|
||||
text, re.IGNORECASE)
|
||||
if match:
|
||||
start = match.group(1) or ''
|
||||
middle = match.group(2) or ''
|
||||
end = match.group(3) or ''
|
||||
html = (start + ('<strong>%s</strong>' % middle) + end)
|
||||
else:
|
||||
html = text
|
||||
self.doc.setHtml(html)
|
||||
|
||||
# Painting item without text, Text Document will paint the text
|
||||
optionV4 = QtGui.QStyleOptionViewItemV4(option)
|
||||
self.initStyleOption(optionV4, index)
|
||||
optionV4.text = QtCore.QString()
|
||||
|
||||
style = QtGui.QApplication.style()
|
||||
style.drawControl(QtGui.QStyle.CE_ItemViewItem, optionV4, painter)
|
||||
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
|
||||
|
||||
# Highlighting text if item is selected
|
||||
if (optionV4.state & QtGui.QStyle.State_Selected):
|
||||
ctx.palette.setColor(QtGui.QPalette.Text, optionV4.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
|
||||
|
||||
# translate the painter to where the text is drawn
|
||||
textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, optionV4)
|
||||
painter.save()
|
||||
|
||||
start = textRect.topLeft() + QtCore.QPoint(3, 0)
|
||||
painter.translate(start)
|
||||
painter.setClipRect(textRect.translated(-start))
|
||||
|
||||
# tell the text document to draw the html for us
|
||||
self.doc.documentLayout().draw(painter, ctx)
|
||||
painter.restore()
|
||||
|
||||
|
||||
class CompletionModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, parent):
|
||||
super(CompletionModel, self).__init__(parent)
|
||||
self.matched_text = ''
|
||||
self.case_sensitive = False
|
||||
|
||||
self.update_thread = GatherCompletionsThread(self)
|
||||
self.connect(self.update_thread, SIGNAL('items_gathered'),
|
||||
self.apply_matches)
|
||||
|
||||
def lower_completion_cmp(self, a, b):
|
||||
return cmp(a.replace('.','').lower(), a.replace('.','').lower())
|
||||
|
||||
def completion_cmp(self, a, b):
|
||||
return cmp(a.replace('.',''), a.replace('.',''))
|
||||
|
||||
def update(self):
|
||||
case_sensitive = self.update_thread.case_sensitive
|
||||
self.update_matches(case_sensitive)
|
||||
|
||||
def set_match_text(self, matched_text, case_sensitive):
|
||||
self.matched_text = matched_text
|
||||
self.update_matches(case_sensitive)
|
||||
|
||||
def update_matches(self, case_sensitive):
|
||||
self.case_sensitive = case_sensitive
|
||||
self.update_thread.case_sensitive = case_sensitive
|
||||
if not self.update_thread.isRunning():
|
||||
self.update_thread.start()
|
||||
|
||||
def gather_matches(self, case_sensitive):
|
||||
return ((), (), set())
|
||||
|
||||
def apply_matches(self, match_tuple):
|
||||
matched_refs, matched_paths, dirs = match_tuple
|
||||
QStandardItem = QtGui.QStandardItem
|
||||
file_icon = qtutils.file_icon()
|
||||
dir_icon = qtutils.dir_icon()
|
||||
git_icon = qtutils.git_icon()
|
||||
|
||||
matched_text = self.matched_text
|
||||
items = []
|
||||
for ref in matched_refs:
|
||||
item = QStandardItem()
|
||||
item.setText(ref)
|
||||
item.setIcon(git_icon)
|
||||
items.append(item)
|
||||
|
||||
if matched_paths and (not matched_text or matched_text in '--'):
|
||||
item = QStandardItem()
|
||||
item.setText('--')
|
||||
item.setIcon(file_icon)
|
||||
items.append(item)
|
||||
|
||||
for match in matched_paths:
|
||||
item = QStandardItem()
|
||||
item.setText(match)
|
||||
if match in dirs:
|
||||
item.setIcon(dir_icon)
|
||||
else:
|
||||
item.setIcon(file_icon)
|
||||
items.append(item)
|
||||
|
||||
self.clear()
|
||||
self.invisibleRootItem().appendRows(items)
|
||||
|
||||
|
||||
class Completer(QtGui.QCompleter):
|
||||
def __init__(self, parent):
|
||||
super(Completer, self).__init__(parent)
|
||||
self._model = None
|
||||
self.setWidget(parent)
|
||||
self.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
|
||||
self.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
|
||||
def setModel(self, model):
|
||||
super(Completer, self).setModel(model)
|
||||
self.connect(model, SIGNAL('updated()'), self.updated)
|
||||
self._model = model
|
||||
|
||||
def model(self):
|
||||
return self._model
|
||||
|
||||
def updated(self):
|
||||
self.model().update()
|
||||
|
||||
def dispose(self):
|
||||
self.model().dispose()
|
||||
|
||||
def set_match_text(self, matched_text, case_sensitive):
|
||||
self.model().set_match_text(matched_text, case_sensitive)
|
||||
|
||||
|
||||
class GitRefCompletionModel(CompletionModel):
|
||||
def __init__(self, parent):
|
||||
super(GitRefCompletionModel, self).__init__(parent)
|
||||
self.cola_model = model = cola.model()
|
||||
msg = model.message_updated
|
||||
model.add_observer(msg, self.emit_updated)
|
||||
|
||||
def emit_updated(self):
|
||||
self.emit(SIGNAL('updated()'))
|
||||
|
||||
def matches(self):
|
||||
model = self.cola_model
|
||||
return model.local_branches + model.remote_branches + model.tags
|
||||
|
||||
def dispose(self):
|
||||
self.cola_model.remove_observer(self.emit_updated)
|
||||
|
||||
def gather_matches(self, case_sensitive):
|
||||
refs = self.matches()
|
||||
matched_text = self.matched_text
|
||||
if matched_text:
|
||||
if not case_sensitive:
|
||||
matched_refs = [r for r in refs if matched_text in r]
|
||||
else:
|
||||
matched_refs = [r for r in refs
|
||||
if matched_text.lower() in r.lower()]
|
||||
else:
|
||||
matched_refs = refs
|
||||
|
||||
if self.case_sensitive:
|
||||
matched_refs.sort(cmp=self.completion_cmp)
|
||||
else:
|
||||
matched_refs.sort(cmp=self.lower_completion_cmp)
|
||||
return (matched_refs, (), set())
|
||||
|
||||
|
||||
class GitLogCompletionModel(GitRefCompletionModel):
|
||||
def __init__(self, parent):
|
||||
super(GitLogCompletionModel, self).__init__(parent)
|
||||
|
||||
def gather_matches(self, case_sensitive):
|
||||
(matched_refs, dummy_paths, dummy_dirs) =\
|
||||
super(GitLogCompletionModel, self).gather_matches(case_sensitive)
|
||||
|
||||
file_list = self.cola_model.everything()
|
||||
files = set(file_list)
|
||||
files_and_dirs = utils.add_parents(set(files))
|
||||
|
||||
dirs = files_and_dirs.difference(files)
|
||||
matched_text = self.matched_text
|
||||
if matched_text:
|
||||
if case_sensitive:
|
||||
matched_paths = [f for f in files_and_dirs
|
||||
if matched_text in f]
|
||||
else:
|
||||
matched_paths = [f for f in files_and_dirs
|
||||
if matched_text.lower() in f.lower()]
|
||||
else:
|
||||
matched_paths = list(files_and_dirs)
|
||||
|
||||
if self.case_sensitive:
|
||||
matched_paths.sort(cmp=self.completion_cmp)
|
||||
else:
|
||||
matched_paths.sort(cmp=self.lower_completion_cmp)
|
||||
return (matched_refs, matched_paths, dirs)
|
||||
|
||||
|
||||
class GitLogCompleter(Completer):
|
||||
def __init__(self, parent):
|
||||
super(GitLogCompleter, self).__init__(parent)
|
||||
self.setModel(GitLogCompletionModel(self))
|
||||
|
||||
|
||||
class GitRefCompleter(Completer):
|
||||
def __init__(self, parent):
|
||||
super(GitRefCompleter, self).__init__(parent)
|
||||
self.setModel(GitRefCompletionModel(self))
|
||||
|
||||
|
||||
class GitLogLineEdit(CompletionLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(GitLogLineEdit, self).__init__(parent)
|
||||
self.setCompleter(GitLogCompleter(self))
|
||||
|
||||
|
||||
class GitRefLineEdit(CompletionLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(GitRefLineEdit, self).__init__(parent)
|
||||
self.setCompleter(GitRefCompleter(self))
|
||||
+132
-50
@@ -1,4 +1,6 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
from cola import gitcmds
|
||||
@@ -6,6 +8,7 @@ from cola import qt
|
||||
from cola import qtutils
|
||||
from cola import utils
|
||||
from cola.widgets import defs
|
||||
from cola.widgets import completion
|
||||
from cola.widgets import standard
|
||||
from cola.main.model import MainModel
|
||||
|
||||
@@ -21,24 +24,85 @@ def create_new_branch(revision=''):
|
||||
return view
|
||||
|
||||
|
||||
class CreateOpts(object):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.reset = False
|
||||
self.track = False
|
||||
self.fetch = True
|
||||
self.checkout = True
|
||||
self.revision = 'HEAD'
|
||||
self.branch = ''
|
||||
|
||||
|
||||
class CreateThread(QtCore.QThread):
|
||||
def __init__(self, opts, parent):
|
||||
super(CreateThread, self).__init__(parent)
|
||||
self.opts = opts
|
||||
|
||||
def run(self):
|
||||
branch = self.opts.branch
|
||||
revision = self.opts.revision
|
||||
reset = self.opts.reset
|
||||
checkout = self.opts.checkout
|
||||
track = self.opts.track
|
||||
model = self.opts.model
|
||||
results = []
|
||||
|
||||
if track and '/' in revision:
|
||||
remote = revision.split('/', 1)[0]
|
||||
status, out = model.git.fetch(remote,
|
||||
with_status=True,
|
||||
with_stderr=True)
|
||||
self.emit(SIGNAL('command'), status, out)
|
||||
results.append(('fetch', status, out))
|
||||
|
||||
if status == 0:
|
||||
status, out = model.create_branch(branch, revision,
|
||||
force=reset,
|
||||
track=track)
|
||||
self.emit(SIGNAL('command'), status, out)
|
||||
|
||||
results.append(('branch', status, out))
|
||||
if status == 0 and checkout:
|
||||
status, out = model.git.checkout(branch,
|
||||
with_status=True,
|
||||
with_stderr=True)
|
||||
self.emit(SIGNAL('command'), status, out)
|
||||
results.append(('checkout', status, out))
|
||||
self.emit(SIGNAL('done'), results)
|
||||
|
||||
class CreateBranchDialog(standard.Dialog):
|
||||
"""A dialog for creating branches."""
|
||||
|
||||
def __init__(self, model, parent=None):
|
||||
standard.Dialog.__init__(self, parent=parent)
|
||||
self.model = model
|
||||
|
||||
super(CreateBranchDialog, self).__init__(parent=parent)
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.setWindowTitle(self.tr('Create Branch'))
|
||||
|
||||
self.model = model
|
||||
self.opts = CreateOpts(model)
|
||||
self.thread = CreateThread(self.opts, self)
|
||||
|
||||
self.progress = QtGui.QProgressDialog(self)
|
||||
self.progress.setRange(0, 0)
|
||||
self.progress.setCancelButton(None)
|
||||
self.progress.setWindowTitle(self.tr('Create Branch'))
|
||||
self.progress.setWindowModality(Qt.WindowModal)
|
||||
|
||||
self.branch_name_label = QtGui.QLabel()
|
||||
self.branch_name_label.setText(self.tr('Branch Name'))
|
||||
|
||||
self.local_branch = QtGui.QLineEdit()
|
||||
self.branch_name = QtGui.QLineEdit()
|
||||
|
||||
self.rev_label = QtGui.QLabel()
|
||||
self.rev_label.setText(self.tr('Revision Expression:'))
|
||||
self.rev_label.setText(self.tr('Starting Revision'))
|
||||
|
||||
self.revision = QtGui.QLineEdit()
|
||||
self.revision = completion.GitRefLineEdit()
|
||||
current = gitcmds.current_branch()
|
||||
if current:
|
||||
self.revision.setText(current)
|
||||
|
||||
self.local_radio = QtGui.QRadioButton()
|
||||
self.local_radio.setText(self.tr('Local Branch'))
|
||||
@@ -85,11 +149,11 @@ class CreateBranchDialog(standard.Dialog):
|
||||
icon=qtutils.git_icon())
|
||||
self.create_button.setDefault(True)
|
||||
|
||||
self.cancel_button = qt.create_button(text='Cancel')
|
||||
self.close_button = qt.create_button(text='Close')
|
||||
|
||||
self.branch_name_layout = QtGui.QHBoxLayout()
|
||||
self.branch_name_layout.addWidget(self.branch_name_label)
|
||||
self.branch_name_layout.addWidget(self.local_branch)
|
||||
self.branch_name_layout.addWidget(self.branch_name)
|
||||
|
||||
self.rev_start_radiobtn_layout = QtGui.QHBoxLayout()
|
||||
self.rev_start_radiobtn_layout.addWidget(self.local_radio)
|
||||
@@ -132,7 +196,7 @@ class CreateBranchDialog(standard.Dialog):
|
||||
self.buttons_layout.setMargin(defs.margin)
|
||||
self.buttons_layout.setSpacing(defs.spacing)
|
||||
self.buttons_layout.addWidget(self.create_button)
|
||||
self.buttons_layout.addWidget(self.cancel_button)
|
||||
self.buttons_layout.addWidget(self.close_button)
|
||||
|
||||
self.options_section_layout = QtGui.QHBoxLayout()
|
||||
self.options_section_layout.setMargin(defs.margin)
|
||||
@@ -148,7 +212,7 @@ class CreateBranchDialog(standard.Dialog):
|
||||
self.main_layout.addLayout(self.options_section_layout)
|
||||
self.setLayout(self.main_layout)
|
||||
|
||||
qtutils.connect_button(self.cancel_button, self.reject)
|
||||
qtutils.connect_button(self.close_button, self.reject)
|
||||
qtutils.connect_button(self.create_button, self.create_branch)
|
||||
qtutils.connect_button(self.local_radio, self.display_model)
|
||||
qtutils.connect_button(self.remote_radio, self.display_model)
|
||||
@@ -157,32 +221,40 @@ class CreateBranchDialog(standard.Dialog):
|
||||
self.connect(self.branch_list, SIGNAL('itemSelectionChanged()'),
|
||||
self.branch_item_changed)
|
||||
|
||||
if self.parent():
|
||||
self.resize(self.parent().width(), self.parent().height())
|
||||
else:
|
||||
self.resize(555, 333)
|
||||
self.connect(self.thread, SIGNAL('command'), self.thread_command)
|
||||
self.connect(self.thread, SIGNAL('done'), self.thread_done)
|
||||
|
||||
self.resize(555, 333)
|
||||
self.display_model()
|
||||
|
||||
def set_revision(self, revision):
|
||||
self.revision.setText(revision)
|
||||
|
||||
def getopts(self):
|
||||
self.opts.revision = self.revision.value()
|
||||
self.opts.branch = unicode(self.branch_name.text())
|
||||
self.opts.checkout = self.checkout_checkbox.isChecked()
|
||||
self.opts.reset = self.reset_radio.isChecked()
|
||||
self.opts.fetch = self.fetch_checkbox.isChecked()
|
||||
self.opts.track = self.remote_radio.isChecked()
|
||||
|
||||
def create_branch(self):
|
||||
"""Creates a branch; called by the "Create Branch" button"""
|
||||
|
||||
revision = unicode(self.revision.text())
|
||||
branch = unicode(self.local_branch.text())
|
||||
existing_branches = self.model.local_branches
|
||||
self.getopts()
|
||||
revision = self.opts.revision
|
||||
branch = self.opts.branch
|
||||
no_update = self.no_update_radio.isChecked()
|
||||
ffwd_only = self.ffwd_only_radio.isChecked()
|
||||
existing_branches = gitcmds.branch_list()
|
||||
check_branch = False
|
||||
|
||||
if not branch or not revision:
|
||||
qtutils.critical('Missing Data',
|
||||
'Please provide both a branch '
|
||||
'name and revision expression.')
|
||||
return
|
||||
|
||||
check_branch = False
|
||||
if branch in existing_branches:
|
||||
if self.no_update_radio.isChecked():
|
||||
if no_update:
|
||||
msg = self.tr("Branch '%s' already exists.")
|
||||
msg = unicode(msg) % branch
|
||||
qtutils.critical('Branch Exists', msg)
|
||||
@@ -192,10 +264,12 @@ class CreateBranchDialog(standard.Dialog):
|
||||
check_branch = bool(commits)
|
||||
|
||||
if check_branch:
|
||||
msg = self.tr("Resetting '%s' to '%s' will "
|
||||
"lose the following commits:")
|
||||
lines = [ unicode(msg) % (branch, revision) ]
|
||||
|
||||
qmsg = self.tr("Resetting '%s' to '%s' will lose commits.")
|
||||
msg = unicode(qmsg) % (branch, revision)
|
||||
if ffwd_only:
|
||||
qtutils.critical('Branch Exists', msg)
|
||||
return
|
||||
lines = [msg]
|
||||
for idx, commit in enumerate(commits):
|
||||
subject = commit[1][0:min(len(commit[1]),16)]
|
||||
if len(subject) < len(commit[1]):
|
||||
@@ -206,11 +280,8 @@ class CreateBranchDialog(standard.Dialog):
|
||||
skip = len(commits) - 5
|
||||
lines.append('\t(%d skipped)' % skip)
|
||||
break
|
||||
|
||||
lines.extend([
|
||||
unicode(self.tr('Recovering lost commits may not be easy.')),
|
||||
])
|
||||
|
||||
line = unicode(self.tr('Recovering lost commits may not be easy.'))
|
||||
lines.append(line)
|
||||
if not qtutils.confirm('Reset Branch?',
|
||||
'\n'.join(lines),
|
||||
'Reset "%s" to "%s"?' % (branch, revision),
|
||||
@@ -218,49 +289,60 @@ class CreateBranchDialog(standard.Dialog):
|
||||
default=False,
|
||||
icon=qtutils.icon('undo.svg')):
|
||||
return
|
||||
self.setEnabled(False)
|
||||
self.progress.setEnabled(True)
|
||||
QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)
|
||||
|
||||
# TODO handle fetch
|
||||
track = self.remote_radio.isChecked()
|
||||
fetch = self.fetch_checkbox.isChecked()
|
||||
ffwd = self.ffwd_only_radio.isChecked()
|
||||
reset = self.reset_radio.isChecked()
|
||||
chkout = self.checkout_checkbox.isChecked()
|
||||
# Show a nice progress bar
|
||||
self.progress.setLabelText('Updating...')
|
||||
self.progress.show()
|
||||
self.thread.start()
|
||||
|
||||
status, output = self.model.create_branch(branch, revision, track=track)
|
||||
def thread_command(self, status, output):
|
||||
qtutils.log(status, output)
|
||||
if status == 0 and chkout:
|
||||
status, output = self.model.git.checkout(branch,
|
||||
with_status=True,
|
||||
with_stderr=True)
|
||||
qtutils.log(status, output)
|
||||
|
||||
def thread_done(self, results):
|
||||
self.setEnabled(True)
|
||||
self.progress.close()
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
|
||||
detail_lines = []
|
||||
for (cmd, status, out) in results:
|
||||
if status != 0:
|
||||
qtutils.critical('Create Branch Error',
|
||||
'"git %s" returned exit status "%d"' %
|
||||
(cmd, status))
|
||||
return
|
||||
line = '"git %s" returned exit status %d' % (cmd, status)
|
||||
detail_lines.append(line)
|
||||
detail_lines.append(out)
|
||||
detail_lines.append('')
|
||||
details = '\n'.join(detail_lines)
|
||||
qtutils.information('Create Branch', 'Branch created',
|
||||
details=details)
|
||||
self.accept()
|
||||
|
||||
def branch_item_changed(self, *rest):
|
||||
"""This callback is called when the branch selection changes"""
|
||||
|
||||
# When the branch selection changes then we should update
|
||||
# the "Revision Expression" accordingly.
|
||||
qlist = self.branch_list
|
||||
(row, selected) = qtutils.selected_row(qlist)
|
||||
if not selected:
|
||||
return
|
||||
|
||||
# Update the model with the selection
|
||||
sources = self.branch_sources()
|
||||
rev = sources[row]
|
||||
|
||||
# Update the model with the selection
|
||||
self.revision.setText(rev)
|
||||
|
||||
# Only set the branch name field if we're
|
||||
# branching from a remote branch.
|
||||
# Set the branch field if we're branching from a remote branch.
|
||||
if not self.remote_radio.isChecked():
|
||||
return
|
||||
branch = utils.basename(rev)
|
||||
if branch == 'HEAD':
|
||||
return
|
||||
|
||||
# Signal that we've clicked on a remote branch
|
||||
self.local_branch.setText(branch)
|
||||
self.branch_name.setText(branch)
|
||||
|
||||
def display_model(self):
|
||||
"""Sets the branch list to the available branches
|
||||
|
||||
+20
-21
@@ -1,5 +1,6 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
|
||||
import cola
|
||||
from cola import qt
|
||||
@@ -9,14 +10,14 @@ from cola.prefs import diff_font
|
||||
from cola.qtutils import connect_button
|
||||
from cola.qtutils import critical
|
||||
from cola.qtutils import information
|
||||
from cola.widgets import completion
|
||||
from cola.widgets import standard
|
||||
from cola.widgets import text
|
||||
|
||||
|
||||
def create_tag(revision=''):
|
||||
"""Entry point for external callers."""
|
||||
opts = TagOptions()
|
||||
if revision:
|
||||
opts.revisions.insert(0, revision)
|
||||
opts = TagOptions(revision)
|
||||
view = CreateTag(opts, qtutils.active_window())
|
||||
view.show()
|
||||
return view
|
||||
@@ -26,18 +27,19 @@ def create_tag(revision=''):
|
||||
class TagOptions(object):
|
||||
"""Simple data container for the CreateTag dialog."""
|
||||
|
||||
def __init__(self):
|
||||
self.revisions = ['HEAD'] + cola.model().all_branches()
|
||||
def __init__(self, revision):
|
||||
self.revision = revision or 'HEAD'
|
||||
|
||||
|
||||
class CreateTag(standard.Dialog):
|
||||
def __init__(self, opts, parent):
|
||||
standard.Dialog.__init__(self, parent=parent)
|
||||
super(CreateTag, self).__init__(parent=parent)
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.setWindowTitle(self.tr('Create Tag'))
|
||||
|
||||
self.opts = opts
|
||||
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
self.setWindowTitle(self.tr('Create Tag'))
|
||||
self.resize(506, 295)
|
||||
self.main_layt = QtGui.QVBoxLayout(self)
|
||||
self.main_layt.setContentsMargins(6, 12, 6, 6)
|
||||
|
||||
@@ -51,7 +53,7 @@ class CreateTag(standard.Dialog):
|
||||
self.input_form_layt.setWidget(0, QtGui.QFormLayout.LabelRole,
|
||||
self.tag_name_label)
|
||||
|
||||
self.tag_name = QtGui.QLineEdit(self)
|
||||
self.tag_name = text.HintedLineEdit('vX.Y.Z', self)
|
||||
self.tag_name.setToolTip(self.tr('Specifies the tag name'))
|
||||
self.input_form_layt.setWidget(0, QtGui.QFormLayout.FieldRole,
|
||||
self.tag_name)
|
||||
@@ -75,10 +77,10 @@ class CreateTag(standard.Dialog):
|
||||
self.input_form_layt.setWidget(2, QtGui.QFormLayout.LabelRole,
|
||||
self.tag_msg_label)
|
||||
|
||||
self.tag_msg = QtGui.QTextEdit(self)
|
||||
self.tag_msg.setAcceptRichText(False)
|
||||
self.tag_msg = text.HintedTextEdit('Tag message...', self)
|
||||
self.tag_msg.setToolTip(self.tr('Specifies the tag message'))
|
||||
self.tag_msg.setFont(diff_font())
|
||||
self.tag_msg.enable_hint(True)
|
||||
self.input_form_layt.setWidget(2, QtGui.QFormLayout.FieldRole,
|
||||
self.tag_msg)
|
||||
# Revision
|
||||
@@ -87,10 +89,8 @@ class CreateTag(standard.Dialog):
|
||||
self.input_form_layt.setWidget(3, QtGui.QFormLayout.LabelRole,
|
||||
self.rev_label)
|
||||
|
||||
self.revision = QtGui.QComboBox()
|
||||
self.revision.addItems(self.opts.revisions)
|
||||
self.revision.setCurrentIndex(0)
|
||||
self.revision.setEditable(True)
|
||||
self.revision = completion.GitRefLineEdit()
|
||||
self.revision.setText(self.opts.revision)
|
||||
self.revision.setToolTip(self.tr('Specifies the SHA-1 to tag'))
|
||||
self.input_form_layt.setWidget(3, QtGui.QFormLayout.FieldRole,
|
||||
self.revision)
|
||||
@@ -110,15 +110,14 @@ class CreateTag(standard.Dialog):
|
||||
connect_button(self.close_button, self.accept)
|
||||
connect_button(self.create_button, self.create_tag)
|
||||
|
||||
def sync_options(self):
|
||||
pass
|
||||
self.resize(506, 295)
|
||||
|
||||
def create_tag(self):
|
||||
"""Verifies inputs and emits a notifier tag message."""
|
||||
|
||||
revision = unicode(self.revision.currentText())
|
||||
tag_name = unicode(self.tag_name.text())
|
||||
tag_msg = unicode(self.tag_msg.toPlainText())
|
||||
revision = self.revision.value()
|
||||
tag_name = self.tag_name.value()
|
||||
tag_msg = self.tag_msg.value()
|
||||
sign_tag = self.sign_tag.isChecked()
|
||||
|
||||
if not revision:
|
||||
|
||||
+30
-9
@@ -1,8 +1,8 @@
|
||||
import fnmatch
|
||||
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
import cola
|
||||
@@ -65,13 +65,14 @@ class RemoteActionDialog(standard.Dialog):
|
||||
def __init__(self, model, action, parent):
|
||||
"""Customizes the dialog based on the remote action
|
||||
"""
|
||||
standard.Dialog.__init__(self, parent=parent)
|
||||
super(RemoteActionDialog, self).__init__(parent=parent)
|
||||
self.model = model
|
||||
self.action = action
|
||||
self.tasks = []
|
||||
|
||||
self.setAttribute(Qt.WA_MacMetalStyle)
|
||||
self.setWindowModality(Qt.WindowModal)
|
||||
self.setWindowTitle(self.tr(action))
|
||||
self.setWindowModality(QtCore.Qt.WindowModal)
|
||||
|
||||
self.progress = QtGui.QProgressDialog(self)
|
||||
self.progress.setRange(0, 0)
|
||||
@@ -80,7 +81,7 @@ class RemoteActionDialog(standard.Dialog):
|
||||
self.progress.setWindowModality(Qt.WindowModal)
|
||||
|
||||
self.local_label = QtGui.QLabel()
|
||||
self.local_label.setText(self.tr('Local Branches'))
|
||||
self.local_label.setText(self.tr('Local Branch'))
|
||||
|
||||
self.local_branch = QtGui.QLineEdit()
|
||||
self.local_branches = QtGui.QListWidget()
|
||||
@@ -93,8 +94,8 @@ class RemoteActionDialog(standard.Dialog):
|
||||
self.remotes = QtGui.QListWidget()
|
||||
self.remotes.addItems(self.model.remotes)
|
||||
|
||||
self.remote_label = QtGui.QLabel()
|
||||
self.remote_label.setText(self.tr('Remote Branch'))
|
||||
self.remote_branch_label = QtGui.QLabel()
|
||||
self.remote_branch_label.setText(self.tr('Remote Branch'))
|
||||
|
||||
self.remote_branch = QtGui.QLineEdit()
|
||||
self.remote_branches = QtGui.QListWidget()
|
||||
@@ -127,7 +128,7 @@ class RemoteActionDialog(standard.Dialog):
|
||||
self.remote_branch_layout.addWidget(self.remote_name)
|
||||
|
||||
self.remote_branches_layout = QtGui.QHBoxLayout()
|
||||
self.remote_branches_layout.addWidget(self.remote_label)
|
||||
self.remote_branches_layout.addWidget(self.remote_branch_label)
|
||||
self.remote_branches_layout.addWidget(self.remote_branch)
|
||||
|
||||
self.options_layout = QtGui.QHBoxLayout()
|
||||
@@ -194,9 +195,12 @@ class RemoteActionDialog(standard.Dialog):
|
||||
self.remote_branch.setFocus()
|
||||
else:
|
||||
self.rebase_checkbox.hide()
|
||||
|
||||
if not qtutils.apply_state(self):
|
||||
self.resize(666, 420)
|
||||
|
||||
self.remote_name.setFocus()
|
||||
|
||||
self.resize(666, 420)
|
||||
|
||||
def set_field_defaults(self):
|
||||
# Default to "git fetch origin master"
|
||||
@@ -350,7 +354,6 @@ class RemoteActionDialog(standard.Dialog):
|
||||
qtutils.log(1, errmsg)
|
||||
return
|
||||
remote, kwargs = self.common_args()
|
||||
action = self.action
|
||||
|
||||
# Check if we're about to create a new branch and warn.
|
||||
remote_branch = unicode(self.remote_branch.text())
|
||||
@@ -449,3 +452,21 @@ class Push(RemoteActionDialog):
|
||||
class Pull(RemoteActionDialog):
|
||||
def __init__(self, model, parent):
|
||||
super(Pull, self).__init__(model, PULL, parent)
|
||||
|
||||
def apply_state(self, state):
|
||||
super(Pull, self).apply_state(state)
|
||||
try:
|
||||
rebase = state['rebase']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.rebase_checkbox.setChecked(rebase)
|
||||
|
||||
def export_state(self):
|
||||
state = super(Pull, self).export_state()
|
||||
state['rebase'] = self.rebase_checkbox.isChecked()
|
||||
return state
|
||||
|
||||
def done(self, exit_code):
|
||||
qtutils.save_state(self)
|
||||
return super(Pull, self).done(exit_code)
|
||||
|
||||
@@ -6,7 +6,6 @@ from PyQt4.QtCore import SIGNAL
|
||||
from cola import gitcmds
|
||||
from cola import qtutils
|
||||
from cola.qt import DiffSyntaxHighlighter
|
||||
from cola.qt import GitRefLineEdit
|
||||
from cola.prefs import diff_font
|
||||
from cola.widgets import defs
|
||||
|
||||
|
||||
+138
-45
@@ -1,57 +1,150 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import Qt
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
|
||||
def create_standard_widget(qtclass):
|
||||
"""Create a standard widget derived from a qt class.
|
||||
"""
|
||||
class StandardWidget(qtclass):
|
||||
# Mix-in for standard view operations
|
||||
def __init__(self, parent=None):
|
||||
self._qtclass = qtclass
|
||||
self._qtclass.__init__(self, parent)
|
||||
def create_widget_class(Base):
|
||||
class Widget(Base):
|
||||
# Mix-in for standard view operations
|
||||
def __init__(self, parent=None):
|
||||
super(Widget, self).__init__(parent)
|
||||
|
||||
def show(self):
|
||||
"""Automatically centers dialogs"""
|
||||
if self.parent():
|
||||
left = self.parent().x()
|
||||
width = self.parent().width()
|
||||
center_x = left + width/2
|
||||
def show(self):
|
||||
"""Automatically centers dialogs"""
|
||||
if self.parent():
|
||||
left = self.parent().x()
|
||||
width = self.parent().width()
|
||||
center_x = left + width/2
|
||||
|
||||
x = center_x - self.width()/2
|
||||
y = self.parent().y()
|
||||
x = center_x - self.width()/2
|
||||
y = self.parent().y()
|
||||
|
||||
self.move(x, y)
|
||||
# Call the base Qt show()
|
||||
self._qtclass.show(self)
|
||||
self.move(x, y)
|
||||
# Call the base Qt show()
|
||||
super(Widget, self).show()
|
||||
|
||||
def name(self):
|
||||
"""Returns the name of the view class"""
|
||||
return self.__class__.__name__.lower()
|
||||
def name(self):
|
||||
"""Returns the name of the view class"""
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
def apply_state(self, state):
|
||||
"""Imports data for view save/restore"""
|
||||
try:
|
||||
self.resize(state['width'], state['height'])
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.move(state['x'], state['y'])
|
||||
except:
|
||||
pass
|
||||
def apply_state(self, state):
|
||||
"""Imports data for view save/restore"""
|
||||
try:
|
||||
self.resize(state['width'], state['height'])
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.move(state['x'], state['y'])
|
||||
except:
|
||||
pass
|
||||
|
||||
def export_state(self):
|
||||
"""Exports data for view save/restore"""
|
||||
return {
|
||||
'x': self.x(),
|
||||
'y': self.y(),
|
||||
'width': self.width(),
|
||||
'height': self.height(),
|
||||
}
|
||||
def export_state(self):
|
||||
"""Exports data for view save/restore"""
|
||||
return {
|
||||
'x': self.x(),
|
||||
'y': self.y(),
|
||||
'width': self.width(),
|
||||
'height': self.height(),
|
||||
}
|
||||
|
||||
return StandardWidget
|
||||
return Widget
|
||||
|
||||
|
||||
# The base class for all cola QDialogs.
|
||||
Widget = create_standard_widget(QtGui.QWidget)
|
||||
Dialog = create_standard_widget(QtGui.QDialog)
|
||||
MainWindow = create_standard_widget(QtGui.QMainWindow)
|
||||
def create_tree_class(Base):
|
||||
class Tree(Base):
|
||||
def __init__(self, parent=None):
|
||||
super(Tree, self).__init__(parent)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setUniformRowHeights(True)
|
||||
self.setAllColumnsShowFocus(True)
|
||||
self.setAnimated(True)
|
||||
|
||||
def key_pressed(self):
|
||||
pass
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Override keyPressEvent to allow LeftArrow to work on non-directories.
|
||||
|
||||
When LeftArrow is pressed on a file entry or an unexpanded directory,
|
||||
then move the current index to the parent directory.
|
||||
|
||||
This simplifies navigation using the keyboard.
|
||||
For power-users, we support Vim keybindings ;-P
|
||||
|
||||
"""
|
||||
# Check whether the item is expanded before calling the base class
|
||||
# keyPressEvent otherwise we end up collapsing and changing the
|
||||
# current index in one shot, which we don't want to do.
|
||||
index = self.currentIndex()
|
||||
was_expanded = self.isExpanded(index)
|
||||
was_collapsed = not was_expanded
|
||||
|
||||
# Vim keybindings...
|
||||
# Rewrite the event before marshalling to QTreeView.event()
|
||||
key = event.key()
|
||||
|
||||
# Remap 'H' to 'Left'
|
||||
if key == Qt.Key_H:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
Qt.Key_Left,
|
||||
event.modifiers())
|
||||
# Remap 'J' to 'Down'
|
||||
elif key == Qt.Key_J:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
Qt.Key_Down,
|
||||
event.modifiers())
|
||||
# Remap 'K' to 'Up'
|
||||
elif key == Qt.Key_K:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
Qt.Key_Up,
|
||||
event.modifiers())
|
||||
# Remap 'L' to 'Right'
|
||||
elif key == Qt.Key_L:
|
||||
event = QtGui.QKeyEvent(event.type(),
|
||||
Qt.Key_Right,
|
||||
event.modifiers())
|
||||
|
||||
# Re-read the event key to take the remappings into account
|
||||
key = event.key()
|
||||
|
||||
result = super(Tree, self).keyPressEvent(event)
|
||||
|
||||
# Let others hook in here before we change the indexes
|
||||
self.emit(SIGNAL('indexAboutToChange()'))
|
||||
|
||||
# Try to select the first item if the model index is invalid
|
||||
if not index.isValid():
|
||||
index = self.model().index(0, 0, QtCore.QModelIndex())
|
||||
if index.isValid():
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
# Automatically select the first entry when expanding a directory
|
||||
elif (key == Qt.Key_Right and was_collapsed and
|
||||
self.isExpanded(index)):
|
||||
index = self.moveCursor(self.MoveDown, event.modifiers())
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
# Process non-root entries with valid parents only.
|
||||
elif key == Qt.Key_Left and index.parent().isValid():
|
||||
|
||||
# File entries have rowCount() == 0
|
||||
if self.model().itemFromIndex(index).rowCount() == 0:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
# Otherwise, do this for collapsed directories only
|
||||
elif was_collapsed:
|
||||
self.setCurrentIndex(index.parent())
|
||||
|
||||
return result
|
||||
# Tree is a closure over "Base"
|
||||
return Tree
|
||||
|
||||
|
||||
Widget = create_widget_class(QtGui.QWidget)
|
||||
Dialog = create_widget_class(QtGui.QDialog)
|
||||
MainWindow = create_widget_class(QtGui.QMainWindow)
|
||||
|
||||
TreeView = create_tree_class(QtGui.QTreeView)
|
||||
TreeWidget = create_tree_class(QtGui.QTreeWidget)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.QtCore import SIGNAL
|
||||
|
||||
|
||||
class HintedTextWidgetEventFilter(QtCore.QObject):
|
||||
def __init__(self, parent):
|
||||
super(HintedTextWidgetEventFilter, self).__init__(parent)
|
||||
self.widget = parent
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QtCore.QEvent.FocusIn:
|
||||
self.widget.emit_position()
|
||||
if self.widget.is_hint():
|
||||
self.widget.enable_hint(False)
|
||||
|
||||
elif event.type() == QtCore.QEvent.FocusOut:
|
||||
if not bool(self.widget.value()):
|
||||
self.widget.enable_hint(True)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class HintedTextWidgetMixin(object):
|
||||
def __init__(self, hint):
|
||||
self._hint = unicode(self.tr(hint))
|
||||
self._event_filter = HintedTextWidgetEventFilter(self)
|
||||
self.installEventFilter(self._event_filter)
|
||||
|
||||
# Palette for normal text
|
||||
self.default_palette = QtGui.QPalette(self.palette())
|
||||
|
||||
# Palette used for the placeholder text
|
||||
self.hint_palette = pal = QtGui.QPalette(self.palette())
|
||||
color = self.hint_palette.text().color()
|
||||
color.setAlpha(128)
|
||||
pal.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
|
||||
pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Text, color)
|
||||
|
||||
def emit_position(self):
|
||||
pass
|
||||
|
||||
def reset_cursor(self):
|
||||
pass
|
||||
|
||||
def hint(self):
|
||||
return self._hint
|
||||
|
||||
def is_hint(self):
|
||||
return self.strip() == self._hint
|
||||
|
||||
def value(self):
|
||||
text = self.strip()
|
||||
if text == self._hint:
|
||||
return u''
|
||||
else:
|
||||
return text
|
||||
|
||||
def strip(self):
|
||||
return self.as_unicode().strip()
|
||||
|
||||
def enable_hint(self, hint):
|
||||
blocksignals = self.blockSignals(True)
|
||||
if hint:
|
||||
self.set_value(self.hint())
|
||||
else:
|
||||
self.clear()
|
||||
self.reset_cursor()
|
||||
self.blockSignals(blocksignals)
|
||||
self.enable_hint_palette(hint)
|
||||
|
||||
def enable_hint_palette(self, hint):
|
||||
if hint:
|
||||
self.setPalette(self.hint_palette)
|
||||
else:
|
||||
self.setPalette(self.default_palette)
|
||||
|
||||
def refresh_palette(self):
|
||||
self.enable_hint_palette(self.is_hint())
|
||||
|
||||
|
||||
class HintedTextEdit(QtGui.QTextEdit, HintedTextWidgetMixin):
|
||||
def __init__(self, hint, parent=None):
|
||||
QtGui.QTextEdit.__init__(self, parent)
|
||||
HintedTextWidgetMixin.__init__(self, hint)
|
||||
|
||||
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
|
||||
self.setAcceptRichText(False)
|
||||
|
||||
self.connect(self, SIGNAL('cursorPositionChanged()'),
|
||||
self.emit_position)
|
||||
|
||||
def as_unicode(self):
|
||||
return unicode(self.toPlainText())
|
||||
|
||||
def set_value(self, value):
|
||||
self.setPlainText(value)
|
||||
|
||||
def emit_position(self):
|
||||
cursor = self.textCursor()
|
||||
position = cursor.position()
|
||||
txt = self.as_unicode()
|
||||
rows = txt[:position].count('\n') + 1
|
||||
cols = cursor.columnNumber()
|
||||
self.emit(SIGNAL('cursorPosition(int,int)'), rows, cols)
|
||||
|
||||
|
||||
class HintedLineEdit(QtGui.QLineEdit, HintedTextWidgetMixin):
|
||||
def __init__(self, hint, parent=None):
|
||||
QtGui.QLineEdit.__init__(self, parent)
|
||||
HintedTextWidgetMixin.__init__(self, hint)
|
||||
|
||||
self.connect(self,
|
||||
SIGNAL('cursorPositionChanged(int,int)'),
|
||||
lambda x, y: self.emit_position())
|
||||
|
||||
def emit_position(self):
|
||||
cols = self.cursorPosition()
|
||||
self.emit(SIGNAL('cursorPosition(int,int)'), 1, cols)
|
||||
|
||||
def set_value(self, value):
|
||||
self.setText(value)
|
||||
|
||||
def as_unicode(self):
|
||||
return unicode(self.text())
|
||||
|
||||
def reset_cursor(self):
|
||||
self.setCursorPosition(0)
|
||||
@@ -37,8 +37,8 @@ SUB-COMMANDS
|
||||
branch::
|
||||
Launch the 'branch' tool.
|
||||
|
||||
classic::
|
||||
Launch the 'classic' tool.
|
||||
browse::
|
||||
Launch the 'browser' tool.
|
||||
|
||||
fetch::
|
||||
Launch the 'fetch' tool.
|
||||
@@ -67,8 +67,8 @@ cola.savewindowsettings::
|
||||
cola.fontdiff::
|
||||
Specifies the font to use for 'git-cola''s diff display.
|
||||
|
||||
cola.classicdockable::
|
||||
Whether to create a dock widget with the 'Cola Classic' tool.
|
||||
cola.browserdockable::
|
||||
Whether to create a dock widget with the 'Browser' tool.
|
||||
Defaults to 'false' to speedup startup time.
|
||||
|
||||
gui.diffcontext::
|
||||
@@ -110,7 +110,7 @@ GIT_COLA_TRACE::
|
||||
|
||||
When defined, 'git-cola' logs 'git' commands to stdout.
|
||||
When set to 'full', 'git-cola' also logs the exit status and output.
|
||||
When set to 'trace', 'git-cola' logs to the 'Command Output' widget.
|
||||
When set to 'trace', 'git-cola' logs to the 'Console' widget.
|
||||
|
||||
SOURCE
|
||||
------
|
||||
|
||||
@@ -154,12 +154,12 @@ span.title {
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Classic actions -->
|
||||
<!-- Browser actions -->
|
||||
<table class="center">
|
||||
<tr>
|
||||
<td width="35%"> </td>
|
||||
<td> </td>
|
||||
<td class="title">Classic actions</td>
|
||||
<td class="title">Browser actions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="shortcut">Ctrl + e</td>
|
||||
|
||||
@@ -1,4 +1,47 @@
|
||||
.. |beta(TM)| unicode:: beta U+2122
|
||||
git-cola v1.7.5
|
||||
===============
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* Auto-completion was added to more tools.
|
||||
|
||||
* `git-dag` is easier to use on smaller displays -- the author
|
||||
field elides its text which allows for a more compact display.
|
||||
|
||||
* Selected commits in `git-dag` were made more prominent and
|
||||
easier to see.
|
||||
|
||||
* 'Create Branch' learned to fetch remote branches and uses a
|
||||
background thread to do so.
|
||||
|
||||
* User-configured GUI tools are listed alphabetically in the 'Actions' menu.
|
||||
|
||||
* The 'Pull' dialog remembers the value of the 'Rebase' checkbox
|
||||
between invocations.
|
||||
|
||||
git-cola v1.7.4.1
|
||||
=================
|
||||
Fixes
|
||||
-----
|
||||
* Detect Homebrew so that OS X users do not need to set PYTHONPATH.
|
||||
|
||||
* `git-dag` can export patches again.
|
||||
|
||||
git-cola v1.7.4
|
||||
===============
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* The 'Classic' tool was renamed to 'Browser' and learned to
|
||||
limit history to the current branch.
|
||||
|
||||
* `git-dag` learned about gravatar and uses it to show images
|
||||
for commit authors.
|
||||
|
||||
* `git-dag` learned to use OpenGL for rendering resulting in
|
||||
much faster rendering.
|
||||
|
||||
* More dialogs learned vim-style keyboard shortcuts.
|
||||
|
||||
* The commit message editor learned better arrow key navigation.
|
||||
|
||||
git-cola v1.7.3
|
||||
===============
|
||||
@@ -55,7 +98,7 @@ Usability, bells and whistles
|
||||
git cola pull
|
||||
git cola tag
|
||||
|
||||
* `git-dag` is more responsive when gathering tab-completions.
|
||||
* `git-dag` is more responsive when gathering auto-completions.
|
||||
|
||||
* Keyboard shortcuts are displayed when the '?' key is pressed.
|
||||
|
||||
@@ -158,8 +201,6 @@ git-cola v1.7.0
|
||||
===============
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* `git-dag` is no longer |beta(TM)|
|
||||
|
||||
* Export a patch series from `git-dag` into a `patches/` directory.
|
||||
|
||||
* `git-dag` learned to diff commits, slice history along paths, etc.
|
||||
@@ -337,7 +378,7 @@ git-cola v1.4.3
|
||||
===============
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* `git-dag` |beta(TM)| now has a separate display area
|
||||
* `git-dag` now has a separate display area
|
||||
for displaying commit metadata. This area will soon
|
||||
grow additional functionality such as cherry-picking,
|
||||
branching, etc.
|
||||
@@ -376,7 +417,7 @@ Usability, bells and whistles
|
||||
* Clicking on paths in the status widget copies them into the
|
||||
copy/paste buffer for easy middle-clicking into terminals.
|
||||
|
||||
* Ctrl+C in diff viewer copies the selected diff to the clipboard.
|
||||
* `Ctrl+C` in diff viewer copies the selected diff to the clipboard.
|
||||
|
||||
Fixes
|
||||
-----
|
||||
@@ -435,7 +476,7 @@ git-cola v1.4.2.2
|
||||
=================
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* `git-dag` |beta(TM)| interaction was made faster.
|
||||
* `git-dag` interaction was made faster.
|
||||
|
||||
Fixes
|
||||
-----
|
||||
@@ -455,7 +496,7 @@ Usability, bells and whistles
|
||||
|
||||
http://github.com/git-cola/git-cola/issues/closed#issue/48
|
||||
|
||||
* `git-dag` |beta(TM)| reads history in a background thread.
|
||||
* `git-dag` reads history in a background thread.
|
||||
|
||||
Portability
|
||||
-----------
|
||||
@@ -469,7 +510,6 @@ Fixes
|
||||
|
||||
git-cola v1.4.2
|
||||
===============
|
||||
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* Added support for the configurable ``guitool.<tool>.*``
|
||||
@@ -552,7 +592,6 @@ Packaging
|
||||
|
||||
git-cola v1.4.1.2
|
||||
=================
|
||||
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* It is now possible to checkout from the index as well
|
||||
@@ -563,7 +602,7 @@ Usability, bells and whistles
|
||||
* Bookmarks can be selected when `git-cola` is run outside of a git repository.
|
||||
* Added more user documentation. We now include many links to
|
||||
external git resources.
|
||||
* Added `git-dag Beta` to the available tools.
|
||||
* Added `git-dag` to the available tools.
|
||||
`git-dag` is a node-based DAG history browser.
|
||||
It doesn't do much yet, but it's been merged so that we can start
|
||||
building and improving upon it.
|
||||
@@ -600,7 +639,6 @@ Packaging
|
||||
|
||||
git-cola v1.4.1.1
|
||||
=================
|
||||
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* We now use patience diff by default when it is available via
|
||||
@@ -619,7 +657,6 @@ Packaging
|
||||
|
||||
git-cola v1.4.1
|
||||
===============
|
||||
|
||||
This feature release adds two new features directly from
|
||||
`git-cola`'s github issues backlog. On the developer
|
||||
front, further work was done towards modularizing the code base.
|
||||
@@ -656,7 +693,6 @@ Developer
|
||||
|
||||
git-cola v1.4.0.5
|
||||
=================
|
||||
|
||||
Fixes
|
||||
-----
|
||||
* Fix launching external applications on Windows
|
||||
@@ -666,7 +702,6 @@ Fixes
|
||||
|
||||
git-cola v1.4.0.4
|
||||
=================
|
||||
|
||||
Packaging
|
||||
---------
|
||||
* Fix Lintian warnings
|
||||
@@ -674,7 +709,6 @@ Packaging
|
||||
|
||||
git-cola v1.4.0.3
|
||||
=================
|
||||
|
||||
Fixes
|
||||
-----
|
||||
* Fix X11 warnings on application startup
|
||||
@@ -682,7 +716,6 @@ Fixes
|
||||
|
||||
git-cola v1.4.0.2
|
||||
=================
|
||||
|
||||
Fixes
|
||||
-----
|
||||
* Added missing 'Exit Diff Mode' button for 'Diff Expression' mode
|
||||
@@ -696,7 +729,6 @@ Fixes
|
||||
|
||||
git-cola v1.4.0.1
|
||||
=================
|
||||
|
||||
Fixes
|
||||
-----
|
||||
* Keep entries in sorted order in the `cola classic` tool
|
||||
@@ -715,7 +747,6 @@ Fixes
|
||||
|
||||
git-cola v1.4.0
|
||||
===============
|
||||
|
||||
This release focuses on a redesign of the git-cola user interface,
|
||||
a tags interface, and better integration of the `cola classic` tool.
|
||||
A flexible interface based on configurable docks is used to manage the
|
||||
@@ -753,7 +784,6 @@ Developer
|
||||
|
||||
git-cola v1.3.9
|
||||
===============
|
||||
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* Added a `cola classic` tool for browsing the entire repository
|
||||
@@ -780,7 +810,6 @@ Fixes
|
||||
|
||||
git-cola v1.3.8
|
||||
===============
|
||||
|
||||
Usability, bells and whistles
|
||||
-----------------------------
|
||||
* Fresh and tasty SVG logos
|
||||
@@ -805,7 +834,6 @@ Packaging
|
||||
|
||||
git-cola v1.3.7
|
||||
===============
|
||||
|
||||
Subsystems
|
||||
----------
|
||||
* `git-difftool` became an official git command in `git-1.6.3`.
|
||||
@@ -837,7 +865,6 @@ Packaging
|
||||
|
||||
git-cola v1.3.6
|
||||
===============
|
||||
|
||||
Subsystems
|
||||
----------
|
||||
* Added support for Kompare in `git-difftool`
|
||||
|
||||
@@ -18,13 +18,13 @@ Main Interface
|
||||
* :command:`ctrl-p` -- Cherry-pick
|
||||
* :command:`shift-alt-s` -- Stash dialog
|
||||
|
||||
Diff Viewer
|
||||
===========
|
||||
Diff
|
||||
====
|
||||
* :command:`h` -- Stage/unstage section under text cursor
|
||||
* :command:`s` -- Stage/unstage selection
|
||||
|
||||
Cola Classic
|
||||
============
|
||||
Browser
|
||||
=======
|
||||
* :command:`h` -- Move to parent/collapse
|
||||
* :command:`j` -- Move down
|
||||
* :command:`k` -- Move up
|
||||
|
||||
@@ -5,6 +5,8 @@ exec wish "$0" -- "$@"
|
||||
# This is a trivial implementation of an SSH_ASKPASS handler.
|
||||
# Git-gui uses this script if none are already configured.
|
||||
|
||||
package require Tk
|
||||
|
||||
set answer {}
|
||||
set yesno 0
|
||||
set rc 255
|
||||
@@ -30,18 +32,19 @@ if {!$yesno} {
|
||||
|
||||
frame .b
|
||||
button .b.ok -text OK -command finish
|
||||
button .b.cancel -text Cancel -command {destroy .}
|
||||
button .b.cancel -text Cancel -command cancel
|
||||
|
||||
pack .b.ok -side left -expand 1
|
||||
pack .b.cancel -side right -expand 1
|
||||
pack .b -side bottom -fill x -padx 10 -pady 10
|
||||
|
||||
bind . <Visibility> {focus -force .e}
|
||||
bind . <Key-Return> finish
|
||||
bind . <Key-Escape> {destroy .}
|
||||
bind . <Destroy> {
|
||||
bind . <Destroy> {}
|
||||
exit $::rc
|
||||
bind . <Key-Return> [list .b.ok invoke]
|
||||
bind . <Key-Escape> [list .b.cancel invoke]
|
||||
bind . <Destroy> {set rc $rc}
|
||||
|
||||
proc cancel {} {
|
||||
set ::rc 255
|
||||
}
|
||||
|
||||
proc finish {} {
|
||||
@@ -53,10 +56,11 @@ proc finish {} {
|
||||
}
|
||||
}
|
||||
|
||||
set ::rc 0
|
||||
puts $::answer
|
||||
destroy .
|
||||
set ::rc 0
|
||||
}
|
||||
|
||||
wm title . "OpenSSH"
|
||||
tk::PlaceWindow .
|
||||
vwait rc
|
||||
exit $rc
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Reference in New Issue
Block a user