1 from qtpy
import QtCore
2 from qtpy
import QtWidgets
11 from ..compat
import ustr
13 from ..models
import prefs
14 from ..models
.prefs
import Defaults
15 from ..models
.prefs
import fallback_editor
18 def preferences(context
, model
=None, parent
=None):
20 model
= prefs
.PreferencesModel(context
)
21 view
= PreferencesView(context
, model
, parent
=parent
)
27 class FormWidget(QtWidgets
.QWidget
):
28 def __init__(self
, context
, model
, parent
, source
='global'):
29 QtWidgets
.QWidget
.__init
__(self
, parent
)
30 self
.context
= context
31 self
.cfg
= context
.cfg
33 self
.config_to_widget
= {}
34 self
.widget_to_config
= {}
37 self
.setLayout(QtWidgets
.QFormLayout())
39 def add_row(self
, label
, widget
):
40 self
.layout().addRow(label
, widget
)
42 def set_config(self
, config_dict
):
43 self
.config_to_widget
.update(config_dict
)
44 for config
, (widget
, default
) in config_dict
.items():
45 self
.widget_to_config
[config
] = widget
46 self
.defaults
[config
] = default
47 self
.connect_widget_to_config(widget
, config
)
49 def connect_widget_to_config(self
, widget
, config
):
50 if isinstance(widget
, QtWidgets
.QSpinBox
):
51 widget
.valueChanged
.connect(self
._int
_config
_changed
(config
))
53 elif isinstance(widget
, QtWidgets
.QCheckBox
):
54 widget
.toggled
.connect(self
._bool
_config
_changed
(config
))
56 elif isinstance(widget
, QtWidgets
.QLineEdit
):
57 widget
.editingFinished
.connect(self
._text
_config
_changed
(config
, widget
))
58 widget
.returnPressed
.connect(self
._text
_config
_changed
(config
, widget
))
60 elif isinstance(widget
, qtutils
.ComboBox
):
61 widget
.currentIndexChanged
.connect(
62 self
._item
_config
_changed
(config
, widget
)
65 def _int_config_changed(self
, config
):
67 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
71 def _bool_config_changed(self
, config
):
73 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
77 def _text_config_changed(self
, config
, widget
):
80 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
84 def _item_config_changed(self
, config
, widget
):
86 value
= widget
.current_data()
87 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
91 def update_from_config(self
):
92 if self
.source
== 'global':
93 getter
= self
.cfg
.get_user_or_system
97 for config
, widget
in self
.widget_to_config
.items():
98 value
= getter(config
)
100 value
= self
.defaults
[config
]
101 set_widget_value(widget
, value
)
104 def set_widget_value(widget
, value
):
105 """Set a value on a widget without emitting notifications"""
106 with qtutils
.BlockSignals(widget
):
107 if isinstance(widget
, QtWidgets
.QSpinBox
):
108 widget
.setValue(value
)
109 elif isinstance(widget
, QtWidgets
.QLineEdit
):
110 widget
.setText(value
)
111 elif isinstance(widget
, QtWidgets
.QCheckBox
):
112 widget
.setChecked(value
)
113 elif hasattr(widget
, 'set_value'):
114 widget
.set_value(value
)
117 class RepoFormWidget(FormWidget
):
118 def __init__(self
, context
, model
, parent
, source
):
119 FormWidget
.__init
__(self
, context
, model
, parent
, source
=source
)
120 self
.name
= QtWidgets
.QLineEdit()
121 self
.email
= QtWidgets
.QLineEdit()
124 'Default directory when exporting patches.\n'
125 'Relative paths are relative to the current repository.\n'
126 'Absolute path are used as-is.'
128 patches_directory
= prefs
.patches_directory(context
)
129 self
.patches_directory
= standard
.DirectoryPathLineEdit(patches_directory
, self
)
130 self
.patches_directory
.setToolTip(tooltip
)
134 This option determines how the supplied commit message should be
135 cleaned up before committing.
137 The <mode> can be strip, whitespace, verbatim, scissors or default.
140 Strip leading and trailing empty lines, trailing whitespace,
141 commentary and collapse consecutive empty lines.
144 Same as strip except #commentary is not removed.
147 Do not change the message at all.
150 Same as whitespace except that everything from (and including) the line
151 found below is truncated, if the message is to be edited.
152 "#" can be customized with core.commentChar.
154 # ------------------------ >8 ------------------------"""
156 self
.commit_cleanup
= qtutils
.combo(
157 prefs
.commit_cleanup_modes(), tooltip
=tooltip
159 self
.diff_context
= standard
.SpinBox(value
=5, mini
=2, maxi
=9995)
160 self
.merge_verbosity
= standard
.SpinBox(value
=5, maxi
=5)
161 self
.merge_summary
= qtutils
.checkbox(checked
=True)
162 self
.autotemplate
= qtutils
.checkbox(checked
=False)
163 self
.merge_diffstat
= qtutils
.checkbox(checked
=True)
164 self
.display_untracked
= qtutils
.checkbox(checked
=True)
165 self
.show_path
= qtutils
.checkbox(checked
=True)
168 'Enable file system change monitoring using '
169 'inotify on Linux and win32event on Windows'
171 self
.inotify
= qtutils
.checkbox(checked
=True)
172 self
.inotify
.setToolTip(tooltip
)
174 self
.logdate
= qtutils
.combo(prefs
.date_formats())
176 'The date-time format used when displaying dates in Git DAG.\n'
177 'This value is passed to git log --date=<format>'
179 self
.logdate
.setToolTip(tooltip
)
181 tooltip
= N_('Use gravatar.com to lookup icons for author emails')
182 self
.enable_gravatar
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
184 tooltip
= N_('Enable path autocompletion in tools')
185 self
.autocomplete_paths
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
187 self
.add_row(N_('User Name'), self
.name
)
188 self
.add_row(N_('Email Address'), self
.email
)
189 self
.add_row(N_('Patches Directory'), self
.patches_directory
)
190 self
.add_row(N_('Log Date Format'), self
.logdate
)
191 self
.add_row(N_('Commit Message Cleanup'), self
.commit_cleanup
)
192 self
.add_row(N_('Merge Verbosity'), self
.merge_verbosity
)
193 self
.add_row(N_('Number of Diff Context Lines'), self
.diff_context
)
194 self
.add_row(N_('Summarize Merge Commits'), self
.merge_summary
)
196 N_('Automatically Load Commit Message Template'), self
.autotemplate
198 self
.add_row(N_('Show Full Paths in the Window Title'), self
.show_path
)
199 self
.add_row(N_('Show Diffstat After Merge'), self
.merge_diffstat
)
200 self
.add_row(N_('Display Untracked Files'), self
.display_untracked
)
201 self
.add_row(N_('Enable Gravatar Icons'), self
.enable_gravatar
)
202 self
.add_row(N_('Autocomplete Paths'), self
.autocomplete_paths
)
205 prefs
.AUTOTEMPLATE
: (self
.autotemplate
, Defaults
.autotemplate
),
206 prefs
.AUTOCOMPLETE_PATHS
: (
207 self
.autocomplete_paths
,
208 Defaults
.autocomplete_paths
,
210 prefs
.COMMIT_CLEANUP
: (self
.commit_cleanup
, Defaults
.commit_cleanup
),
211 prefs
.DIFFCONTEXT
: (self
.diff_context
, Defaults
.diff_context
),
212 prefs
.DISPLAY_UNTRACKED
: (
213 self
.display_untracked
,
214 Defaults
.display_untracked
,
216 prefs
.ENABLE_GRAVATAR
: (self
.enable_gravatar
, Defaults
.enable_gravatar
),
217 prefs
.INOTIFY
: (self
.inotify
, Defaults
.inotify
),
218 prefs
.LOGDATE
: (self
.logdate
, Defaults
.logdate
),
219 prefs
.MERGE_DIFFSTAT
: (self
.merge_diffstat
, Defaults
.merge_diffstat
),
220 prefs
.MERGE_SUMMARY
: (self
.merge_summary
, Defaults
.merge_summary
),
221 prefs
.MERGE_VERBOSITY
: (self
.merge_verbosity
, Defaults
.merge_verbosity
),
222 prefs
.PATCHES_DIRECTORY
: (
223 self
.patches_directory
,
224 Defaults
.patches_directory
,
226 prefs
.SHOW_PATH
: (self
.show_path
, Defaults
.show_path
),
227 prefs
.USER_NAME
: (self
.name
, ''),
228 prefs
.USER_EMAIL
: (self
.email
, ''),
232 class SettingsFormWidget(FormWidget
):
233 def __init__(self
, context
, model
, parent
):
234 FormWidget
.__init
__(self
, context
, model
, parent
)
236 self
.fixed_font
= QtWidgets
.QFontComboBox()
237 self
.font_size
= standard
.SpinBox(value
=12, mini
=6, maxi
=192)
238 self
.maxrecent
= standard
.SpinBox(maxi
=99)
239 self
.tabwidth
= standard
.SpinBox(maxi
=42)
240 self
.textwidth
= standard
.SpinBox(maxi
=150)
242 self
.editor
= QtWidgets
.QLineEdit()
243 self
.historybrowser
= QtWidgets
.QLineEdit()
244 self
.blameviewer
= QtWidgets
.QLineEdit()
245 self
.difftool
= QtWidgets
.QLineEdit()
246 self
.mergetool
= QtWidgets
.QLineEdit()
248 self
.linebreak
= qtutils
.checkbox()
249 self
.mouse_zoom
= qtutils
.checkbox()
250 self
.keep_merge_backups
= qtutils
.checkbox()
251 self
.sort_bookmarks
= qtutils
.checkbox()
252 self
.save_window_settings
= qtutils
.checkbox()
253 self
.check_spelling
= qtutils
.checkbox()
254 tooltip
= N_('Detect conflict markers in unmerged files')
255 self
.check_conflicts
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
256 self
.expandtab
= qtutils
.checkbox(tooltip
=N_('Insert tabs instead of spaces'))
257 tooltip
= N_('Prevent "Stage" from staging all files when nothing is selected')
258 self
.safe_mode
= qtutils
.checkbox(checked
=False, tooltip
=tooltip
)
259 tooltip
= N_('Check whether a commit has been published when amending')
260 self
.check_published_commits
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
262 'Refresh repository state whenever the window is focused or un-minimized'
264 self
.refresh_on_focus
= qtutils
.checkbox(checked
=False, tooltip
=tooltip
)
265 self
.resize_browser_columns
= qtutils
.checkbox(checked
=False)
266 tooltip
= N_('Emit notifications when commits are pushed.')
267 self
.notifyonpush
= qtutils
.checkbox(checked
=False, tooltip
=tooltip
)
269 self
.add_row(N_('Fixed-Width Font'), self
.fixed_font
)
270 self
.add_row(N_('Font Size'), self
.font_size
)
271 self
.add_row(N_('Text Width'), self
.textwidth
)
272 self
.add_row(N_('Tab Width'), self
.tabwidth
)
273 self
.add_row(N_('Editor'), self
.editor
)
274 self
.add_row(N_('History Browser'), self
.historybrowser
)
275 self
.add_row(N_('Blame Viewer'), self
.blameviewer
)
276 self
.add_row(N_('Diff Tool'), self
.difftool
)
277 self
.add_row(N_('Merge Tool'), self
.mergetool
)
278 self
.add_row(N_('Recent repository count'), self
.maxrecent
)
279 self
.add_row(N_('Auto-Wrap Lines'), self
.linebreak
)
280 self
.add_row(N_('Insert spaces instead of tabs'), self
.expandtab
)
282 N_('Check Published Commits when Amending'), self
.check_published_commits
284 self
.add_row(N_('Sort bookmarks alphabetically'), self
.sort_bookmarks
)
285 self
.add_row(N_('Safe Mode'), self
.safe_mode
)
286 self
.add_row(N_('Detect Conflict Markers'), self
.check_conflicts
)
287 self
.add_row(N_('Keep *.orig Merge Backups'), self
.keep_merge_backups
)
288 self
.add_row(N_('Save GUI Settings'), self
.save_window_settings
)
289 self
.add_row(N_('Refresh on Focus'), self
.refresh_on_focus
)
290 self
.add_row(N_('Resize File Browser columns'), self
.resize_browser_columns
)
291 self
.add_row(N_('Check spelling'), self
.check_spelling
)
292 self
.add_row(N_('Ctrl+MouseWheel to Zoom'), self
.mouse_zoom
)
293 self
.add_row(N_('Notify on Push'), self
.notifyonpush
)
296 prefs
.SAVEWINDOWSETTINGS
: (
297 self
.save_window_settings
,
298 Defaults
.save_window_settings
,
300 prefs
.TABWIDTH
: (self
.tabwidth
, Defaults
.tabwidth
),
301 prefs
.EXPANDTAB
: (self
.expandtab
, Defaults
.expandtab
),
302 prefs
.TEXTWIDTH
: (self
.textwidth
, Defaults
.textwidth
),
303 prefs
.LINEBREAK
: (self
.linebreak
, Defaults
.linebreak
),
304 prefs
.MAXRECENT
: (self
.maxrecent
, Defaults
.maxrecent
),
305 prefs
.SORT_BOOKMARKS
: (self
.sort_bookmarks
, Defaults
.sort_bookmarks
),
306 prefs
.DIFFTOOL
: (self
.difftool
, Defaults
.difftool
),
307 prefs
.EDITOR
: (self
.editor
, fallback_editor()),
308 prefs
.HISTORY_BROWSER
: (
310 prefs
.default_history_browser(),
312 prefs
.BLAME_VIEWER
: (self
.blameviewer
, Defaults
.blame_viewer
),
313 prefs
.CHECK_CONFLICTS
: (self
.check_conflicts
, Defaults
.check_conflicts
),
314 prefs
.CHECK_PUBLISHED_COMMITS
: (
315 self
.check_published_commits
,
316 Defaults
.check_published_commits
,
318 prefs
.MERGE_KEEPBACKUP
: (
319 self
.keep_merge_backups
,
320 Defaults
.merge_keep_backup
,
322 prefs
.MERGETOOL
: (self
.mergetool
, Defaults
.mergetool
),
323 prefs
.REFRESH_ON_FOCUS
: (self
.refresh_on_focus
, Defaults
.refresh_on_focus
),
324 prefs
.RESIZE_BROWSER_COLUMNS
: (
325 self
.resize_browser_columns
,
326 Defaults
.resize_browser_columns
,
328 prefs
.SAFE_MODE
: (self
.safe_mode
, Defaults
.safe_mode
),
329 prefs
.SPELL_CHECK
: (self
.check_spelling
, Defaults
.spellcheck
),
330 prefs
.MOUSE_ZOOM
: (self
.mouse_zoom
, Defaults
.mouse_zoom
),
331 prefs
.NOTIFY_ON_PUSH
: (self
.notifyonpush
, Defaults
.notifyonpush
),
334 self
.fixed_font
.currentFontChanged
.connect(self
.current_font_changed
)
335 self
.font_size
.valueChanged
.connect(self
.font_size_changed
)
337 def update_from_config(self
):
338 """Update widgets to the current config values"""
339 FormWidget
.update_from_config(self
)
340 context
= self
.context
342 with qtutils
.BlockSignals(self
.fixed_font
):
343 font
= qtutils
.diff_font(context
)
344 self
.fixed_font
.setCurrentFont(font
)
346 with qtutils
.BlockSignals(self
.font_size
):
347 font_size
= font
.pointSize()
348 self
.font_size
.setValue(font_size
)
350 def font_size_changed(self
, size
):
351 font
= self
.fixed_font
.currentFont()
352 font
.setPointSize(size
)
353 cmds
.do(prefs
.SetConfig
, self
.model
, 'global', prefs
.FONTDIFF
, font
.toString())
355 def current_font_changed(self
, font
):
356 cmds
.do(prefs
.SetConfig
, self
.model
, 'global', prefs
.FONTDIFF
, font
.toString())
359 class AppearanceFormWidget(FormWidget
):
360 def __init__(self
, context
, model
, parent
):
361 FormWidget
.__init
__(self
, context
, model
, parent
)
363 self
.themes
= themes
.get_all_themes()
364 self
.theme
= qtutils
.combo_mapped(themes
.options(themes
=self
.themes
))
365 self
.icon_theme
= qtutils
.combo_mapped(icons
.icon_themes())
367 # The transform to ustr is needed because the config reader will convert
368 # "0", "1", and "2" into integers. The "1.5" value, though, is
369 # parsed as a string, so the transform is effectively a no-op.
370 self
.high_dpi
= qtutils
.combo_mapped(hidpi
.options(), transform
=ustr
)
371 self
.high_dpi
.setEnabled(hidpi
.is_supported())
372 self
.bold_headers
= qtutils
.checkbox()
373 self
.status_show_totals
= qtutils
.checkbox()
374 self
.status_indent
= qtutils
.checkbox()
375 self
.block_cursor
= qtutils
.checkbox(checked
=True)
377 self
.add_row(N_('GUI theme'), self
.theme
)
378 self
.add_row(N_('Icon theme'), self
.icon_theme
)
379 self
.add_row(N_('High DPI'), self
.high_dpi
)
380 self
.add_row(N_('Bold on dark headers instead of italic'), self
.bold_headers
)
381 self
.add_row(N_('Show file counts in Status titles'), self
.status_show_totals
)
382 self
.add_row(N_('Indent Status paths'), self
.status_indent
)
383 self
.add_row(N_('Use a block cursor in diff editors'), self
.block_cursor
)
386 prefs
.BOLD_HEADERS
: (self
.bold_headers
, Defaults
.bold_headers
),
387 prefs
.HIDPI
: (self
.high_dpi
, Defaults
.hidpi
),
388 prefs
.STATUS_SHOW_TOTALS
: (
389 self
.status_show_totals
,
390 Defaults
.status_show_totals
,
392 prefs
.STATUS_INDENT
: (self
.status_indent
, Defaults
.status_indent
),
393 prefs
.THEME
: (self
.theme
, Defaults
.theme
),
394 prefs
.ICON_THEME
: (self
.icon_theme
, Defaults
.icon_theme
),
395 prefs
.BLOCK_CURSOR
: (self
.block_cursor
, Defaults
.block_cursor
),
398 self
.theme
.currentIndexChanged
.connect(self
._theme
_changed
)
400 def _theme_changed(self
, theme_idx
):
401 """Set the icon theme to dark/light when the main theme changes"""
402 # Set the icon theme to a theme that corresponds to the main settings.
404 theme
= self
.themes
[theme_idx
]
407 icon_theme
= self
.icon_theme
.current_data()
408 if theme
.name
== 'default':
409 if icon_theme
in ('light', 'dark'):
410 self
.icon_theme
.set_value('default')
412 if icon_theme
in ('default', 'light'):
413 self
.icon_theme
.set_value('dark')
414 elif not theme
.is_dark
:
415 if icon_theme
in ('default', 'dark'):
416 self
.icon_theme
.set_value('light')
419 class AppearanceWidget(QtWidgets
.QWidget
):
420 def __init__(self
, form
, parent
):
421 QtWidgets
.QWidget
.__init
__(self
, parent
)
423 self
.label
= QtWidgets
.QLabel(
425 + N_('Restart the application after changing appearance settings.')
428 layout
= qtutils
.vbox(
436 self
.setLayout(layout
)
438 def update_from_config(self
):
439 self
.form
.update_from_config()
442 class PreferencesView(standard
.Dialog
):
443 def __init__(self
, context
, model
, parent
=None):
444 standard
.Dialog
.__init
__(self
, parent
=parent
)
445 self
.context
= context
446 self
.setWindowTitle(N_('Preferences'))
447 if parent
is not None:
448 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
450 self
.resize(600, 360)
452 self
.tab_bar
= QtWidgets
.QTabBar()
453 self
.tab_bar
.setDrawBase(False)
454 self
.tab_bar
.addTab(N_('All Repositories'))
455 self
.tab_bar
.addTab(N_('Current Repository'))
456 self
.tab_bar
.addTab(N_('Settings'))
457 self
.tab_bar
.addTab(N_('Appearance'))
459 self
.user_form
= RepoFormWidget(context
, model
, self
, source
='global')
460 self
.repo_form
= RepoFormWidget(context
, model
, self
, source
='local')
461 self
.options_form
= SettingsFormWidget(context
, model
, self
)
462 self
.appearance_form
= AppearanceFormWidget(context
, model
, self
)
463 self
.appearance
= AppearanceWidget(self
.appearance_form
, self
)
465 self
.stack_widget
= QtWidgets
.QStackedWidget()
466 self
.stack_widget
.addWidget(self
.user_form
)
467 self
.stack_widget
.addWidget(self
.repo_form
)
468 self
.stack_widget
.addWidget(self
.options_form
)
469 self
.stack_widget
.addWidget(self
.appearance
)
471 self
.close_button
= qtutils
.close_button()
473 self
.button_layout
= qtutils
.hbox(
474 defs
.no_margin
, defs
.spacing
, qtutils
.STRETCH
, self
.close_button
477 self
.main_layout
= qtutils
.vbox(
484 self
.setLayout(self
.main_layout
)
486 self
.tab_bar
.currentChanged
.connect(self
.stack_widget
.setCurrentIndex
)
487 self
.stack_widget
.currentChanged
.connect(self
.update_widget
)
489 qtutils
.connect_button(self
.close_button
, self
.accept
)
490 qtutils
.add_close_action(self
)
492 self
.update_widget(0)
494 def update_widget(self
, idx
):
495 widget
= self
.stack_widget
.widget(idx
)
496 widget
.update_from_config()