qt: Make the 'text' field optional in create_button()
[git-cola.git] / cola / app.py
blob62cc8ed6fbc92f4f7d59e97e149a25c75a81921e
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.controllers.createtag import create_tag
35 from cola.classic import cola_classic
36 from cola.dag import git_dag
37 from cola.stash import stash
38 from cola.decorators import memoize
39 from cola.main.view import MainView
40 from cola.main.controller import MainController
41 from cola.widgets import cfgactions
42 from cola.widgets import startup
45 def setup_environment():
46 # Spoof an X11 display for SSH
47 os.environ.setdefault('DISPLAY', ':0')
49 # Provide an SSH_ASKPASS fallback
50 if sys.platform == 'darwin':
51 os.environ.setdefault('SSH_ASKPASS',
52 resources.share('bin', 'ssh-askpass-darwin'))
53 else:
54 os.environ.setdefault('SSH_ASKPASS',
55 resources.share('bin', 'ssh-askpass'))
57 # Setup the path so that git finds us when we run 'git cola'
58 path_entries = os.environ.get('PATH').split(os.pathsep)
59 bindir = os.path.dirname(os.path.abspath(__file__))
60 path_entries.insert(0, bindir)
61 path = os.pathsep.join(path_entries)
62 os.environ['PATH'] = path
63 os.putenv('PATH', path)
66 @memoize
67 def instance(argv):
68 return QtGui.QApplication(list(argv))
71 # style note: we use camelCase here since we're masquerading a Qt class
72 class ColaApplication(object):
73 """The main cola application
75 ColaApplication handles i18n of user-visible data
76 """
78 def __init__(self, argv, locale=None, gui=True):
79 """Initialize our QApplication for translation
80 """
81 i18n.install(locale)
82 qtcompat.install()
84 # Add the default style dir so that we find our icons
85 icon_dir = resources.icon_dir()
86 qtcompat.add_search_path(os.path.basename(icon_dir), icon_dir)
88 # monkey-patch Qt's translate() to use our translate()
89 if gui:
90 self._app = instance(tuple(argv))
91 self._app.setWindowIcon(qtutils.git_icon())
92 self._translate_base = QtGui.QApplication.translate
93 QtGui.QApplication.translate = self.translate
94 else:
95 self._app = QtCore.QCoreApplication(argv)
96 self._translate_base = QtCore.QCoreApplication.translate
97 QtCore.QCoreApplication.translate = self.translate
99 # Register model commands
100 cmds.register()
102 # Make file descriptors binary for win32
103 utils.set_binary(sys.stdin)
104 utils.set_binary(sys.stdout)
105 utils.set_binary(sys.stderr)
107 def translate(self, domain, txt):
109 Translate strings with gettext
111 Supports @@noun/@@verb specifiers.
114 trtxt = i18n.gettext(txt)
115 if trtxt[-6:-4] == '@@': # handle @@verb / @@noun
116 trtxt = trtxt[:-6]
117 return trtxt
119 def activeWindow(self):
120 """Wrap activeWindow()"""
121 return self._app.activeWindow()
123 def exec_(self):
124 """Wrap exec_()"""
125 return self._app.exec_()
128 def parse_args(context):
129 parser = optparse.OptionParser(usage='%prog [options]')
131 # We also accept 'git cola version'
132 parser.add_option('-v', '--version',
133 help='Show cola version',
134 dest='version',
135 default=False,
136 action='store_true')
138 # Specifies a git repository to open
139 parser.add_option('-r', '--repo',
140 help='Specifies the path to a git repository.',
141 dest='repo',
142 metavar='PATH',
143 default=os.getcwd())
145 # Specifies that we should prompt for a repository at startup
146 parser.add_option('--prompt',
147 help='Prompt for a repository before starting the main GUI.',
148 dest='prompt',
149 action='store_true',
150 default=False)
152 # Used on Windows for adding 'git' to the path
153 parser.add_option('-g', '--git-path',
154 help='Specifies the path to the git binary',
155 dest='git',
156 metavar='PATH',
157 default='')
159 if context == 'git-dag':
160 parser.add_option('-c', '--count',
161 help='Number of commits to display.',
162 dest='count',
163 type='int',
164 default=1000)
166 return parser.parse_args()
169 def process_args(opts, args):
170 if opts.version or (args and args[0] == 'version'):
171 # Accept 'git cola --version' or 'git cola version'
172 print 'cola version', version.version()
173 sys.exit(0)
175 if opts.git:
176 # Adds git to the PATH. This is needed on Windows.
177 path_entries = os.environ.get('PATH', '').split(os.pathsep)
178 path_entries.insert(0, os.path.dirname(opts.git))
179 os.environ['PATH'] = os.pathsep.join(path_entries)
181 # Bail out if --repo is not a directory
182 repo = os.path.realpath(opts.repo)
183 if not os.path.isdir(repo):
184 print >> sys.stderr, "fatal: '%s' is not a directory. Consider supplying -r <path>.\n" % repo
185 sys.exit(-1)
187 # We do everything relative to the repo root
188 os.chdir(opts.repo)
190 return repo
193 def main(context):
194 """Parses the command-line arguments and starts git-cola
196 setup_environment()
197 opts, args = parse_args(context)
198 repo = process_args(opts, args)
200 # Allow Ctrl-C to exit
201 signal.signal(signal.SIGINT, signal.SIG_DFL)
203 # Initialize the app
204 app = ColaApplication(sys.argv)
206 # Ensure that we're working in a valid git repository.
207 # If not, try to find one. When found, chdir there.
208 model = cola.model()
209 valid = model.set_worktree(repo) and not opts.prompt
210 while not valid:
211 startup_dlg = startup.StartupDialog(app.activeWindow())
212 gitdir = startup_dlg.find_git_repo()
213 if not gitdir:
214 sys.exit(-1)
215 valid = model.set_worktree(gitdir)
217 # Finally, go to the root of the git repo
218 os.chdir(model.git.worktree())
220 # Prepare to launch a sub-command
221 builtins = set(('cola',
222 'classic',
223 'dag',
224 'fetch',
225 'pull',
226 'push',
227 'stash',
228 'tag'))
230 if context != 'git-dag' and args and args[0] in builtins:
231 context = args[0]
233 # Show the GUI
234 if context == 'git-cola' or context == 'cola':
235 view = MainView(model, qtutils.active_window())
236 ctl = MainController(model, view)
237 elif context == 'git-dag' or context == 'dag':
238 ctl = git_dag(model, opts=opts, args=args)
239 view = ctl.view
240 elif context == 'classic':
241 view = cola_classic(update=False)
242 # TODO: the calls to update_status() can be done asynchronously
243 # by hooking into the message_updated notification.
244 elif context == 'stash':
245 model.update_status()
246 view = stash().view
247 elif context == 'fetch':
248 model.update_status()
249 view = guicmds.fetch().view
250 elif context == 'pull':
251 model.update_status()
252 view = guicmds.pull().view
253 elif context == 'push':
254 model.update_status()
255 view = guicmds.push().view
256 elif context == 'tag':
257 view = create_tag().view
259 # Install UI wrappers for command objects
260 cfgactions.install_command_wrapper()
261 guicmds.install_command_wrapper()
263 # Make sure that we start out on top
264 view.show()
265 view.raise_()
267 # Scan for the first time
268 task = _start_update_thread(model)
270 # Start the inotify thread
271 inotify.start()
273 msg_timer = QtCore.QTimer()
274 msg_timer.setSingleShot(True)
275 msg_timer.connect(msg_timer, SIGNAL('timeout()'), _send_msg)
276 msg_timer.start(0)
278 # Start the event loop
279 result = app.exec_()
281 # All done, cleanup
282 inotify.stop()
283 QtCore.QThreadPool.globalInstance().waitForDone()
285 pattern = cola.model().tmp_file_pattern()
286 for filename in glob.glob(pattern):
287 os.unlink(filename)
288 sys.exit(result)
290 return ctl, task
293 def _start_update_thread(model):
294 """Update the model in the background
296 git-cola should startup as quickly as possible.
299 class UpdateTask(QtCore.QRunnable):
300 def run(self):
301 model.update_status(update_index=True)
303 # Hold onto a reference to prevent PyQt from dereferencing
304 task = UpdateTask()
305 QtCore.QThreadPool.globalInstance().start(task)
307 return task
310 def _send_msg():
311 if git.GIT_COLA_TRACE == 'trace':
312 msg = ('info: Trace enabled. '
313 'Many of commands reported with "trace" use git\'s stable '
314 '"plumbing" API and are not intended for typical '
315 'day-to-day use. Here be dragons')
316 cola.notifier().broadcast(signals.log_cmd, 0, msg)