core: make getcwd() fail-safe
[git-cola.git] / cola / widgets / about.py
blob0bdda97b8d3947ca3a9001b49aa02846687a2bcc
1 # encoding: utf-8
2 from __future__ import division, absolute_import, unicode_literals
3 import platform
4 import webbrowser
5 import sys
7 import qtpy
8 from qtpy.QtCore import Qt
9 from qtpy import QtGui
10 from qtpy import QtWidgets
12 from ..i18n import N_
13 from .. import core
14 from .. import resources
15 from .. import hotkeys
16 from .. import icons
17 from .. import qtutils
18 from .. import version
19 from . import defs
22 def about_dialog(context):
23 """Launches the Help -> About dialog"""
24 view = AboutView(context, qtutils.active_window())
25 view.show()
26 return view
29 class ExpandingTabBar(QtWidgets.QTabBar):
30 """A TabBar with tabs that expand to fill the empty space
32 The setExpanding(True) method does not work in practice because
33 it respects the OS style. We override the style by implementing
34 tabSizeHint() so that we can specify the size explicitly.
36 """
38 def tabSizeHint(self, tab_index):
39 width = self.parent().width() / max(1, self.count()) - 1
40 size = super(ExpandingTabBar, self).tabSizeHint(tab_index)
41 size.setWidth(width)
42 return size
45 class ExpandingTabWidget(QtWidgets.QTabWidget):
47 def __init__(self, parent=None):
48 super(ExpandingTabWidget, self).__init__(parent)
49 self.setTabBar(ExpandingTabBar(self))
51 def resizeEvent(self, event):
52 """Forward resize events to the ExpandingTabBar"""
53 # Qt does not resize the tab bar when the dialog is resized
54 # so manually forward resize events to the tab bar.
55 width = event.size().width()
56 height = self.tabBar().height()
57 self.tabBar().resize(width, height)
58 return super(ExpandingTabWidget, self).resizeEvent(event)
61 class AboutView(QtWidgets.QDialog):
62 """Provides the git-cola 'About' dialog"""
64 def __init__(self, context, parent=None):
65 QtWidgets.QDialog.__init__(self, parent)
67 self.context = context
68 self.setWindowTitle(N_('About git-cola'))
69 self.setWindowModality(Qt.WindowModal)
71 # Top-most large icon
72 logo_pixmap = icons.cola().pixmap(defs.huge_icon, defs.large_icon)
74 self.logo_label = QtWidgets.QLabel()
75 self.logo_label.setPixmap(logo_pixmap)
76 self.logo_label.setAlignment(Qt.AlignCenter)
78 self.logo_text_label = QtWidgets.QLabel()
79 self.logo_text_label.setText('Git Cola')
80 self.logo_text_label.setAlignment(Qt.AlignLeft | Qt.AlignCenter)
82 font = self.logo_text_label.font()
83 font.setPointSize(defs.logo_text)
84 self.logo_text_label.setFont(font)
86 self.text = qtutils.textbrowser(text=copyright_text())
87 self.version = qtutils.textbrowser(text=version_text(context))
88 self.authors = qtutils.textbrowser(text=authors_text())
89 self.translators = qtutils.textbrowser(text=translators_text())
91 self.tabs = ExpandingTabWidget()
92 self.tabs.addTab(self.text, N_('About'))
93 self.tabs.addTab(self.version, N_('Version'))
94 self.tabs.addTab(self.authors, N_('Authors'))
95 self.tabs.addTab(self.translators, N_('Translators'))
97 self.close_button = qtutils.close_button()
98 self.close_button.setDefault(True)
100 self.logo_layout = qtutils.hbox(defs.no_margin, defs.button_spacing,
101 self.logo_label, self.logo_text_label,
102 qtutils.STRETCH)
104 self.button_layout = qtutils.hbox(defs.spacing, defs.margin,
105 qtutils.STRETCH, self.close_button)
107 self.main_layout = qtutils.vbox(defs.no_margin, defs.spacing,
108 self.logo_layout,
109 self.tabs,
110 self.button_layout)
111 self.setLayout(self.main_layout)
113 qtutils.connect_button(self.close_button, self.accept)
115 self.resize(defs.scale(600), defs.scale(720))
118 def copyright_text():
119 return """
120 Git Cola: The highly caffeinated Git GUI
122 Copyright (C) 2007-2018 David Aguilar and contributors
124 This program is free software: you can redistribute it and/or
125 modify it under the terms of the GNU General Public License
126 version 2 as published by the Free Software Foundation.
128 This program is distributed in the hope that it will
129 be useful, but WITHOUT ANY WARRANTY; without even the
130 implied warranty of MERCHANTABILITY or
131 FITNESS FOR A PARTICULAR PURPOSE.
133 See the GNU General Public License for more details.
135 You should have received a copy of the
136 GNU General Public License along with this program.
137 If not, see http://www.gnu.org/licenses/.
142 def version_text(context):
143 git_version = version.git_version(context)
144 cola_version = version.version()
145 build_version = version.build_version()
146 python_path = sys.executable
147 python_version = sys.version
148 qt_version = qtpy.QT_VERSION
149 qtpy_version = qtpy.__version__
150 pyqt_api_name = qtpy.API_NAME
151 if qtpy.PYQT5 or qtpy.PYQT4:
152 pyqt_api_version = qtpy.PYQT_VERSION
153 elif qtpy.PYSIDE:
154 pyqt_api_version = qtpy.PYSIDE_VERSION
155 else:
156 pyqt_api_version = 'unknown'
158 platform_version = platform.platform()
160 # Only show the build version if _build_version.py exists
161 if build_version:
162 build_version = '(%s)' % build_version
163 else:
164 build_version = ''
166 scope = dict(
167 cola_version=cola_version,
168 build_version=build_version,
169 git_version=git_version,
170 platform_version=platform_version,
171 pyqt_api_name=pyqt_api_name,
172 pyqt_api_version=pyqt_api_version,
173 python_path=python_path,
174 python_version=python_version,
175 qt_version=qt_version,
176 qtpy_version=qtpy_version)
178 return N_("""
179 <br>
180 Git Cola version %(cola_version)s %(build_version)s
181 <ul>
182 <li> %(platform_version)s
183 <li> Python (%(python_path)s) %(python_version)s
184 <li> Git %(git_version)s
185 <li> Qt %(qt_version)s
186 <li> QtPy %(qtpy_version)s
187 <li> %(pyqt_api_name)s %(pyqt_api_version)s
188 </ul>
189 """) % scope
192 def link(url, text, palette=None):
193 if palette is None:
194 palette = QtGui.QPalette()
196 color = palette.color(QtGui.QPalette.Foreground)
197 rgb = 'rgb(%s, %s, %s)' % (color.red(), color.green(), color.blue())
198 scope = dict(rgb=rgb, text=text, url=url)
200 return """
201 <a style="font-style: italic; text-decoration: none; color: %(rgb)s;"
202 href="%(url)s">
203 %(text)s
204 </a>
205 """ % scope
208 def mailto(email, text, palette):
209 return link('mailto:%s' % email, text, palette) + '<br>'
212 def render_authors(authors):
213 """Render a list of author details into richtext html"""
214 for x in authors:
215 x.setdefault('email', '')
217 entries = [("""
219 <strong>%(name)s</strong><br>
220 <em>%(title)s</em><br>
221 %(email)s
222 </p>
223 """ % author) for author in authors]
225 return ''.join(entries)
228 def contributors_text(authors, prelude='', epilogue=''):
229 author_text = render_authors(authors)
230 scope = dict(author_text=author_text, epilogue=epilogue, prelude=prelude)
232 return """
233 %(prelude)s
234 %(author_text)s
235 %(epilogue)s
236 """ % scope
239 def authors_text():
240 palette = QtGui.QPalette()
241 contact = N_('Email contributor')
242 authors = (
243 # The names listed here are listed in the same order as
244 # `git shortlog --summary --numbered --no-merges`
245 # Please submit a pull request if you would like to include your
246 # email address in the about screen.
247 # See the `generate-about` script in the "todo" branch.
248 # vim :read! ./Meta/generate-about
249 dict(name='David Aguilar',
250 title=N_('Maintainer (since 2007) and developer'),
251 email=mailto('davvid@gmail.com', contact, palette)),
252 dict(name='Daniel Harding', title=N_('Developer')),
253 dict(name='V字龍(Vdragon)', title=N_('Developer'),
254 email=mailto('Vdragon.Taiwan@gmail.com', contact, palette)),
255 dict(name='Efimov Vasily', title=N_('Developer')),
256 dict(name='Guillaume de Bure', title=N_('Developer')),
257 dict(name='Uri Okrent', title=N_('Developer')),
258 dict(name='Alex Chernetz', title=N_('Developer')),
259 dict(name='Andreas Sommer', title=N_('Developer')),
260 dict(name='Thomas Kluyver', title=N_('Developer')),
261 dict(name='Javier Rodriguez Cuevas', title=N_('Developer')),
262 dict(name='Minarto Margoliono', title=N_('Developer')),
263 dict(name='Szymon Judasz', title=N_('Developer')),
264 dict(name='Ville Skyttä', title=N_('Developer')),
265 dict(name='Igor Galarraga', title=N_('Developer')),
266 dict(name='Stanislaw Halik', title=N_('Developer')),
267 dict(name='Virgil Dupras', title=N_('Developer')),
268 dict(name='Barry Roberts', title=N_('Developer')),
269 dict(name='wsdfhjxc', title=N_('Developer')),
270 dict(name='xhl', title=N_('Developer')),
271 dict(name='Stefan Naewe', title=N_('Developer')),
272 dict(name='Benedict Lee', title=N_('Developer')),
273 dict(name='Filip Danilović', title=N_('Developer')),
274 dict(name='Pavel Rehak', title=N_('Developer')),
275 dict(name='Steffen Prohaska', title=N_('Developer')),
276 dict(name='Michael Geddes', title=N_('Developer')),
277 dict(name='Rustam Safin', title=N_('Developer')),
278 dict(name='Alex Gulyás', title=N_('Developer')),
279 dict(name='David Martínez Martí', title=N_('Developer')),
280 dict(name='Justin Lecher', title=N_('Developer')),
281 dict(name='Kai Krakow', title=N_('Developer')),
282 dict(name='Karl Bielefeldt', title=N_('Developer')),
283 dict(name='Marco Costalba', title=N_('Developer')),
284 dict(name='Michael Homer', title=N_('Developer')),
285 dict(name='Sebastian Schuberth', title=N_('Developer')),
286 dict(name='Sven Claussner', title=N_('Developer')),
287 dict(name='real', title=N_('Developer')),
288 dict(name='v.paritskiy', title=N_('Developer')),
289 dict(name='林博仁(Buo-ren Lin)', title=N_('Developer')),
290 dict(name='AJ Bagwell', title=N_('Developer')),
291 dict(name='Adrien be', title=N_('Developer')),
292 dict(name='Andrej', title=N_('Developer')),
293 dict(name='Audrius Karabanovas', title=N_('Developer')),
294 dict(name='Barrett Lowe', title=N_('Developer')),
295 dict(name='Ben Boeckel', title=N_('Developer')),
296 dict(name='Boris W', title=N_('Developer')),
297 dict(name='Charles', title=N_('Developer')),
298 dict(name='Clément Pit--Claudel', title=N_('Developer')),
299 dict(name='Daniel Haskin', title=N_('Developer')),
300 dict(name='Daniel King', title=N_('Developer')),
301 dict(name='Daniel Pavel', title=N_('Developer')),
302 dict(name='David Zumbrunnen', title=N_('Developer')),
303 dict(name='George Vasilakos', title=N_('Developer')),
304 dict(name='Ilya Tumaykin', title=N_('Developer')),
305 dict(name='Iulian Udrea', title=N_('Developer')),
306 dict(name='Jake Biesinger', title=N_('Developer')),
307 dict(name='Jakub Szymański', title=N_('Developer')),
308 dict(name='Jamie Pate', title=N_('Developer')),
309 dict(name='Jean-Francois Dagenais', title=N_('Developer')),
310 dict(name='Karthik Manamcheri', title=N_('Developer')),
311 dict(name='Kelvie Wong', title=N_('Developer')),
312 dict(name='Kyle', title=N_('Developer')),
313 dict(name='Maciej Filipiak', title=N_('Developer')),
314 dict(name='Maicon D. Filippsen', title=N_('Developer')),
315 dict(name='Markus Heidelberg', title=N_('Developer')),
316 dict(name='Matthew E. Levine', title=N_('Developer')),
317 dict(name='Matthias Mailänder', title=N_('Developer')),
318 dict(name='Md. Mahbub Alam', title=N_('Developer')),
319 dict(name='Mikhail Terekhov', title=N_('Developer')),
320 dict(name='Paul Hildebrandt', title=N_('Developer')),
321 dict(name='Paul Weingardt', title=N_('Developer')),
322 dict(name='Paulo Fidalgo', title=N_('Developer')),
323 dict(name='Petr Gladkikh', title=N_('Developer')),
324 dict(name='Philip Stark', title=N_('Developer')),
325 dict(name='Radek Postołowicz', title=N_('Developer')),
326 dict(name='Rainer Müller', title=N_('Developer')),
327 dict(name='Rolando Espinoza', title=N_('Developer')),
328 dict(name="Samsul Ma'arif", title=N_('Developer')),
329 dict(name='Sebastian Brass', title=N_('Developer')),
330 dict(name='Vaibhav Sagar', title=N_('Developer')),
331 dict(name='Ved Vyas', title=N_('Developer')),
332 dict(name='Voicu Hodrea', title=N_('Developer')),
333 dict(name='Wesley Wong', title=N_('Developer')),
334 dict(name='Wolfgang Ocker', title=N_('Developer')),
335 dict(name='Zhang Han', title=N_('Developer')),
336 dict(name='beauxq', title=N_('Developer')),
337 dict(name='ochristi', title=N_('Developer')),
339 bug_url = 'https://github.com/git-cola/git-cola/issues'
340 bug_link = link(bug_url, bug_url)
341 scope = dict(bug_link=bug_link)
342 prelude = N_("""
343 <br>
344 Please use %(bug_link)s to report issues.
345 <br>
346 """) % scope
348 return contributors_text(authors, prelude=prelude)
351 def translators_text():
352 palette = QtGui.QPalette()
353 contact = N_('Email contributor')
355 translators = (
356 # See the `generate-about` script in the "todo" branch.
357 # vim :read! ./Meta/generate-about --translators
358 dict(name='V字龍(Vdragon)',
359 title=N_('Traditional Chinese (Taiwan) translation'),
360 email=mailto('Vdragon.Taiwan@gmail.com', contact, palette)),
361 dict(name='Pavel Rehak',
362 title=N_('Czech translation')),
363 dict(name='Vitor Lobo',
364 title=N_('Brazilian translation')),
365 dict(name='Zhang Han',
366 title=N_('Simplified Chinese translation')),
367 dict(name='Igor Kopach',
368 title=N_('Ukranian translation')),
369 dict(name='Barış ÇELİK',
370 title=N_('Turkish translation')),
371 dict(name='Minarto Margoliono',
372 title=N_('Indonesian translation')),
373 dict(name='Rafael Nascimento',
374 title=N_('Brazilian translation')),
375 dict(name='Sven Claussner',
376 title=N_('German translation')),
377 dict(name='Vaiz',
378 title=N_('Russian translation')),
379 dict(name='Victorhck',
380 title=N_('Spanish translation')),
381 dict(name='Guo Yunhe',
382 title=N_('Simplified Chinese translation')),
383 dict(name='Kai Krakow',
384 title=N_('German translation')),
385 dict(name='Louis Rousseau',
386 title=N_('French translation')),
387 dict(name='Mickael Albertus',
388 title=N_('French translation')),
389 dict(name='Peter Dave Hello',
390 title=N_('Traditional Chinese (Taiwan) translation')),
391 dict(name='Pilar Molina Lopez',
392 title=N_('Spanish translation')),
393 dict(name="Samsul Ma'arif",
394 title=N_('Indonesian translation')),
395 dict(name='Zeioth',
396 title=N_('Spanish translation')),
397 dict(name='balping',
398 title=N_('Hungarian translation')),
399 dict(name='p-bo',
400 title=N_('Czech translation')),
401 dict(name='Łukasz Wojniłowicz',
402 title=N_('Polish translation')),
405 bug_url = 'https://github.com/git-cola/git-cola/issues'
406 bug_link = link(bug_url, bug_url)
407 scope = dict(bug_link=bug_link)
409 prelude = N_("""
410 <br>
411 Git Cola has been translated into different languages thanks
412 to the help of the individuals listed below.
414 <br>
416 Translation is approximate. If you find a mistake,
417 please let us know by opening an issue on Github:
418 </p>
421 %(bug_link)s
422 </p>
424 <br>
426 We invite you to participate in translation by adding or updating
427 a translation and opening a pull request.
428 </p>
430 <br>
432 """) % scope
433 return contributors_text(translators, prelude=prelude)
436 def show_shortcuts():
437 hotkeys_html = resources.doc(N_('hotkeys.html'))
438 try:
439 from qtpy import QtWebEngineWidgets
440 except (ImportError, qtpy.PythonQtError):
441 # redhat disabled QtWebKit in their qt build but don't punish the users
442 webbrowser.open_new_tab('file://' + hotkeys_html)
443 return
445 html = core.read(hotkeys_html)
447 parent = qtutils.active_window()
448 widget = QtWidgets.QDialog()
449 widget.setWindowModality(Qt.WindowModal)
450 widget.setWindowTitle(N_('Shortcuts'))
452 web = QtWebEngineWidgets.QWebEngineView(parent)
453 web.setHtml(html)
455 layout = qtutils.hbox(defs.no_margin, defs.spacing, web)
456 widget.setLayout(layout)
457 widget.resize(800, min(parent.height(), 600))
458 qtutils.add_action(widget, N_('Close'), widget.accept,
459 hotkeys.QUESTION, *hotkeys.ACCEPT)
460 widget.show()
461 widget.exec_()