widgets.commitmsg: Rename signal to avoid overloading Qt's signal
[git-cola.git] / cola / app.py
blobe8a1ef51a95943faca11f9e81727a6cae2512d65
1 # Copyright (C) 2009, David Aguilar <davvid@gmail.com>
2 """Provides the main() routine and ColaApplicaiton"""
4 import glob
5 import optparse
6 import os
7 import signal
8 import sys
10 try:
11 from PyQt4 import QtGui
12 from PyQt4 import QtCore
13 from PyQt4.QtCore import SIGNAL
14 except ImportError:
15 print >> sys.stderr, 'Sorry, you do not seem to have PyQt4 installed.'
16 print >> sys.stderr, 'Please install it before using git-cola.'
17 print >> sys.stderr, 'e.g.: sudo apt-get install python-qt4'
18 sys.exit(-1)
21 # Import cola modules
22 import cola
23 from cola import cmds
24 from cola import git
25 from cola import guicmds
26 from cola import inotify
27 from cola import i18n
28 from cola import qtcompat
29 from cola import qtutils
30 from cola import resources
31 from cola import signals
32 from cola import utils
33 from cola import version
34 from cola.classic import cola_classic
35 from cola.dag import git_dag
36 from cola.stash import stash
37 from cola.decorators import memoize
38 from cola.main.view import MainView
39 from cola.main.controller import MainController
40 from cola.widgets import remote
41 from cola.widgets import cfgactions
42 from cola.widgets import startup
43 from cola.widgets.createtag import create_tag
44 from cola.widgets.createbranch import create_new_branch
45 from cola.widgets.search import search
48 def setup_environment():
49 # Spoof an X11 display for SSH
50 os.environ.setdefault('DISPLAY', ':0')
52 # Provide an SSH_ASKPASS fallback
53 if sys.platform == 'darwin':
54 os.environ.setdefault('SSH_ASKPASS',
55 resources.share('bin', 'ssh-askpass-darwin'))
56 else:
57 os.environ.setdefault('SSH_ASKPASS',
58 resources.share('bin', 'ssh-askpass'))
60 # Setup the path so that git finds us when we run 'git cola'
61 path_entries = os.environ.get('PATH').split(os.pathsep)
62 bindir = os.path.dirname(os.path.abspath(__file__))
63 path_entries.insert(0, bindir)
64 path = os.pathsep.join(path_entries)
65 os.environ['PATH'] = path
66 os.putenv('PATH', path)
69 @memoize
70 def instance(argv):
71 return QtGui.QApplication(list(argv))
74 # style note: we use camelCase here since we're masquerading a Qt class
75 class ColaApplication(object):
76 """The main cola application
78 ColaApplication handles i18n of user-visible data
79 """
81 def __init__(self, argv, locale=None, gui=True):
82 """Initialize our QApplication for translation
83 """
84 i18n.install(locale)
85 qtcompat.install()
87 # Add the default style dir so that we find our icons
88 icon_dir = resources.icon_dir()
89 qtcompat.add_search_path(os.path.basename(icon_dir), icon_dir)
91 # monkey-patch Qt's translate() to use our translate()
92 if gui:
93 self._app = instance(tuple(argv))
94 self._app.setWindowIcon(qtutils.git_icon())
95 self._translate_base = QtGui.QApplication.translate
96 QtGui.QApplication.translate = self.translate
97 else:
98 self._app = QtCore.QCoreApplication(argv)
99 self._translate_base = QtCore.QCoreApplication.translate
100 QtCore.QCoreApplication.translate = self.translate
102 # Register model commands
103 cmds.register()
105 # Make file descriptors binary for win32
106 utils.set_binary(sys.stdin)
107 utils.set_binary(sys.stdout)
108 utils.set_binary(sys.stderr)
110 def translate(self, domain, txt):
112 Translate strings with gettext
114 Supports @@noun/@@verb specifiers.
117 trtxt = i18n.gettext(txt)
118 if trtxt[-6:-4] == '@@': # handle @@verb / @@noun
119 trtxt = trtxt[:-6]
120 return trtxt
122 def activeWindow(self):
123 """Wrap activeWindow()"""
124 return self._app.activeWindow()
126 def exec_(self):
127 """Wrap exec_()"""
128 return self._app.exec_()
131 def parse_args(context):
132 args = sys.argv[1:]
133 builtins = set(('branch',
134 'browse',
135 'classic',
136 'dag',
137 'fetch',
138 'pull',
139 'push',
140 'stash',
141 'search',
142 'tag'))
143 if context == 'git-dag':
144 context = 'dag'
145 elif args and args[0] in builtins:
146 context = args.pop(0)
147 sys.argv = sys.argv[0:1] + args
149 parser = optparse.OptionParser(usage='%prog [options]')
151 # We also accept 'git cola version'
152 parser.add_option('-v', '--version',
153 help='Show cola version',
154 dest='version',
155 default=False,
156 action='store_true')
158 # Specifies a git repository to open
159 parser.add_option('-r', '--repo',
160 help='Specifies the path to a git repository.',
161 dest='repo',
162 metavar='PATH',
163 default=os.getcwd())
165 # Specifies that we should prompt for a repository at startup
166 parser.add_option('--prompt',
167 help='Prompt for a repository before starting the main GUI.',
168 dest='prompt',
169 action='store_true',
170 default=False)
172 # Used on Windows for adding 'git' to the path
173 parser.add_option('-g', '--git-path',
174 help='Specifies the path to the git binary',
175 dest='git',
176 metavar='PATH',
177 default='')
179 if context == 'dag':
180 parser.add_option('-c', '--count',
181 help='Number of commits to display.',
182 dest='count',
183 type='int',
184 default=1000)
186 opts, args = parser.parse_args()
187 return opts, args, context
190 def process_args(opts, args):
191 if opts.version or (args and args[0] == 'version'):
192 # Accept 'git cola --version' or 'git cola version'
193 print 'cola version', version.version()
194 sys.exit(0)
196 if opts.git:
197 # Adds git to the PATH. This is needed on Windows.
198 path_entries = os.environ.get('PATH', '').split(os.pathsep)
199 path_entries.insert(0, os.path.dirname(opts.git))
200 os.environ['PATH'] = os.pathsep.join(path_entries)
202 # Bail out if --repo is not a directory
203 repo = os.path.realpath(opts.repo)
204 if not os.path.isdir(repo):
205 print >> sys.stderr, "fatal: '%s' is not a directory. Consider supplying -r <path>.\n" % repo
206 sys.exit(-1)
208 # We do everything relative to the repo root
209 os.chdir(opts.repo)
211 return repo
214 def main(context):
215 """Parses the command-line arguments and starts git-cola
217 setup_environment()
218 opts, args, context = parse_args(context)
219 repo = process_args(opts, args)
221 # Allow Ctrl-C to exit
222 signal.signal(signal.SIGINT, signal.SIG_DFL)
224 # Initialize the app
225 app = ColaApplication(sys.argv)
227 # Ensure that we're working in a valid git repository.
228 # If not, try to find one. When found, chdir there.
229 model = cola.model()
230 valid = model.set_worktree(repo) and not opts.prompt
231 while not valid:
232 startup_dlg = startup.StartupDialog(app.activeWindow())
233 gitdir = startup_dlg.find_git_repo()
234 if not gitdir:
235 sys.exit(-1)
236 valid = model.set_worktree(gitdir)
238 # Finally, go to the root of the git repo
239 os.chdir(model.git.worktree())
241 # Show the GUI
242 if context == 'branch':
243 view = create_new_branch()
244 elif context in ('git-dag', 'dag'):
245 ctl = git_dag(model, opts=opts, args=args)
246 view = ctl.view
247 elif context in ('classic', 'browse'):
248 view = cola_classic(update=False)
249 # TODO: the calls to update_status() can be done asynchronously
250 # by hooking into the message_updated notification.
251 elif context == 'fetch':
252 model.update_status()
253 view = remote.fetch()
254 elif context == 'pull':
255 model.update_status()
256 view = remote.pull()
257 elif context == 'push':
258 model.update_status()
259 view = remote.push()
260 elif context == 'search':
261 view = search()
262 elif context == 'stash':
263 model.update_status()
264 view = stash().view
265 elif context == 'tag':
266 view = create_tag()
267 else:
268 view = MainView(model, qtutils.active_window())
269 ctl = MainController(model, view)
271 # Install UI wrappers for command objects
272 cfgactions.install_command_wrapper()
273 guicmds.install_command_wrapper()
275 # Make sure that we start out on top
276 view.show()
277 view.raise_()
279 # Scan for the first time
280 task = _start_update_thread(model)
282 # Start the inotify thread
283 inotify.start()
285 msg_timer = QtCore.QTimer()
286 msg_timer.setSingleShot(True)
287 msg_timer.connect(msg_timer, SIGNAL('timeout()'), _send_msg)
288 msg_timer.start(0)
290 # Start the event loop
291 result = app.exec_()
293 # All done, cleanup
294 inotify.stop()
295 QtCore.QThreadPool.globalInstance().waitForDone()
297 pattern = utils.tmp_file_pattern()
298 for filename in glob.glob(pattern):
299 os.unlink(filename)
300 sys.exit(result)
302 return ctl, task
305 def _start_update_thread(model):
306 """Update the model in the background
308 git-cola should startup as quickly as possible.
311 class UpdateTask(QtCore.QRunnable):
312 def run(self):
313 model.update_status(update_index=True)
315 # Hold onto a reference to prevent PyQt from dereferencing
316 task = UpdateTask()
317 QtCore.QThreadPool.globalInstance().start(task)
319 return task
322 def _send_msg():
323 if git.GIT_COLA_TRACE == 'trace':
324 msg = ('info: Trace enabled. '
325 'Many of commands reported with "trace" use git\'s stable '
326 '"plumbing" API and are not intended for typical '
327 'day-to-day use. Here be dragons')
328 cola.notifier().broadcast(signals.log_cmd, 0, msg)