1 from __future__
import absolute_import
, division
, print_function
, unicode_literals
3 from qtpy
import QtCore
4 from qtpy
import QtWidgets
11 from .. import qtutils
13 from ..compat
import ustr
15 from ..models
import prefs
16 from ..models
.prefs
import Defaults
17 from ..models
.prefs
import fallback_editor
20 def preferences(context
, model
=None, parent
=None):
22 model
= prefs
.PreferencesModel(context
)
23 view
= PreferencesView(context
, model
, parent
=parent
)
29 class FormWidget(QtWidgets
.QWidget
):
30 def __init__(self
, context
, model
, parent
, source
='global'):
31 QtWidgets
.QWidget
.__init
__(self
, parent
)
32 self
.context
= context
33 self
.cfg
= context
.cfg
35 self
.config_to_widget
= {}
36 self
.widget_to_config
= {}
39 self
.setLayout(QtWidgets
.QFormLayout())
41 def add_row(self
, label
, widget
):
42 self
.layout().addRow(label
, widget
)
44 def set_config(self
, config_dict
):
45 self
.config_to_widget
.update(config_dict
)
46 for config
, (widget
, default
) in config_dict
.items():
47 self
.widget_to_config
[config
] = widget
48 self
.defaults
[config
] = default
49 self
.connect_widget_to_config(widget
, config
)
51 def connect_widget_to_config(self
, widget
, config
):
52 if isinstance(widget
, QtWidgets
.QSpinBox
):
53 widget
.valueChanged
.connect(self
._int
_config
_changed
(config
))
55 elif isinstance(widget
, QtWidgets
.QCheckBox
):
56 widget
.toggled
.connect(self
._bool
_config
_changed
(config
))
58 elif isinstance(widget
, QtWidgets
.QLineEdit
):
59 widget
.editingFinished
.connect(self
._text
_config
_changed
(config
, widget
))
60 widget
.returnPressed
.connect(self
._text
_config
_changed
(config
, widget
))
62 elif isinstance(widget
, qtutils
.ComboBox
):
63 widget
.currentIndexChanged
.connect(
64 self
._item
_config
_changed
(config
, widget
)
67 def _int_config_changed(self
, config
):
69 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
73 def _bool_config_changed(self
, config
):
75 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
79 def _text_config_changed(self
, config
, widget
):
82 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
86 def _item_config_changed(self
, config
, widget
):
88 value
= widget
.current_data()
89 cmds
.do(prefs
.SetConfig
, self
.model
, self
.source
, config
, value
)
93 def update_from_config(self
):
94 if self
.source
== 'global':
95 getter
= self
.cfg
.get_user_or_system
99 for config
, widget
in self
.widget_to_config
.items():
100 value
= getter(config
)
102 value
= self
.defaults
[config
]
103 set_widget_value(widget
, value
)
106 def set_widget_value(widget
, value
):
107 """Set a value on a widget without emitting notifications"""
108 with qtutils
.BlockSignals(widget
):
109 if isinstance(widget
, QtWidgets
.QSpinBox
):
110 widget
.setValue(value
)
111 elif isinstance(widget
, QtWidgets
.QLineEdit
):
112 widget
.setText(value
)
113 elif isinstance(widget
, QtWidgets
.QCheckBox
):
114 widget
.setChecked(value
)
115 elif isinstance(widget
, qtutils
.ComboBox
):
116 widget
.set_value(value
)
119 class RepoFormWidget(FormWidget
):
120 def __init__(self
, context
, model
, parent
, source
):
121 FormWidget
.__init
__(self
, context
, model
, parent
, source
=source
)
122 self
.name
= QtWidgets
.QLineEdit()
123 self
.email
= QtWidgets
.QLineEdit()
125 self
.diff_context
= standard
.SpinBox(value
=5, mini
=2, maxi
=9995)
126 self
.merge_verbosity
= standard
.SpinBox(value
=5, maxi
=5)
127 self
.merge_summary
= qtutils
.checkbox(checked
=True)
128 self
.autotemplate
= qtutils
.checkbox(checked
=False)
129 self
.merge_diffstat
= qtutils
.checkbox(checked
=True)
130 self
.display_untracked
= qtutils
.checkbox(checked
=True)
131 self
.show_path
= qtutils
.checkbox(checked
=True)
132 self
.tabwidth
= standard
.SpinBox(value
=8, maxi
=42)
133 self
.textwidth
= standard
.SpinBox(value
=72, maxi
=150)
135 tooltip
= N_('Detect conflict markers in unmerged files')
136 self
.check_conflicts
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
138 tooltip
= N_('Use gravatar.com to lookup icons for author emails')
139 self
.enable_gravatar
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
141 tooltip
= N_('Prevent "Stage" from staging all files when nothing is selected')
142 self
.safe_mode
= qtutils
.checkbox(checked
=False, tooltip
=tooltip
)
144 tooltip
= N_('Enable path autocompletion in tools')
145 self
.autocomplete_paths
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
147 tooltip
= N_('Check whether a commit has been published when amending')
148 self
.check_published_commits
= qtutils
.checkbox(checked
=True, tooltip
=tooltip
)
150 self
.add_row(N_('User Name'), self
.name
)
151 self
.add_row(N_('Email Address'), self
.email
)
152 self
.add_row(N_('Tab Width'), self
.tabwidth
)
153 self
.add_row(N_('Text Width'), self
.textwidth
)
154 self
.add_row(N_('Merge Verbosity'), self
.merge_verbosity
)
155 self
.add_row(N_('Number of Diff Context Lines'), self
.diff_context
)
156 self
.add_row(N_('Summarize Merge Commits'), self
.merge_summary
)
158 N_('Automatically Load Commit Message Template'), self
.autotemplate
160 self
.add_row(N_('Show Full Paths in the Window Title'), self
.show_path
)
161 self
.add_row(N_('Show Diffstat After Merge'), self
.merge_diffstat
)
162 self
.add_row(N_('Display Untracked Files'), self
.display_untracked
)
163 self
.add_row(N_('Detect Conflict Markers'), self
.check_conflicts
)
164 self
.add_row(N_('Enable Gravatar Icons'), self
.enable_gravatar
)
165 self
.add_row(N_('Safe Mode'), self
.safe_mode
)
166 self
.add_row(N_('Autocomplete Paths'), self
.autocomplete_paths
)
168 N_('Check Published Commits when Amending'), self
.check_published_commits
173 prefs
.AUTOTEMPLATE
: (self
.autotemplate
, Defaults
.autotemplate
),
174 prefs
.CHECK_CONFLICTS
: (self
.check_conflicts
, Defaults
.check_conflicts
),
175 prefs
.ENABLE_GRAVATAR
: (self
.enable_gravatar
, Defaults
.enable_gravatar
),
176 prefs
.CHECK_PUBLISHED_COMMITS
: (
177 self
.check_published_commits
,
178 Defaults
.check_published_commits
,
180 prefs
.DIFFCONTEXT
: (self
.diff_context
, Defaults
.diff_context
),
181 prefs
.DISPLAY_UNTRACKED
: (
182 self
.display_untracked
,
183 Defaults
.display_untracked
,
185 prefs
.USER_NAME
: (self
.name
, ''),
186 prefs
.USER_EMAIL
: (self
.email
, ''),
187 prefs
.MERGE_DIFFSTAT
: (self
.merge_diffstat
, Defaults
.merge_diffstat
),
188 prefs
.MERGE_SUMMARY
: (self
.merge_summary
, Defaults
.merge_summary
),
189 prefs
.MERGE_VERBOSITY
: (self
.merge_verbosity
, Defaults
.merge_verbosity
),
190 prefs
.SAFE_MODE
: (self
.safe_mode
, Defaults
.safe_mode
),
191 prefs
.AUTOCOMPLETE_PATHS
: (
192 self
.autocomplete_paths
,
193 Defaults
.autocomplete_paths
,
195 prefs
.SHOW_PATH
: (self
.show_path
, Defaults
.show_path
),
196 prefs
.TABWIDTH
: (self
.tabwidth
, Defaults
.tabwidth
),
197 prefs
.TEXTWIDTH
: (self
.textwidth
, Defaults
.textwidth
),
202 class SettingsFormWidget(FormWidget
):
203 def __init__(self
, context
, model
, parent
):
204 FormWidget
.__init
__(self
, context
, model
, parent
)
206 self
.fixed_font
= QtWidgets
.QFontComboBox()
207 self
.font_size
= standard
.SpinBox(value
=12, mini
=8, maxi
=192)
209 self
.maxrecent
= standard
.SpinBox(maxi
=99)
210 self
.tabwidth
= standard
.SpinBox(maxi
=42)
211 self
.textwidth
= standard
.SpinBox(maxi
=150)
213 self
.editor
= QtWidgets
.QLineEdit()
214 self
.historybrowser
= QtWidgets
.QLineEdit()
215 self
.blameviewer
= QtWidgets
.QLineEdit()
216 self
.difftool
= QtWidgets
.QLineEdit()
217 self
.mergetool
= QtWidgets
.QLineEdit()
219 self
.linebreak
= qtutils
.checkbox()
220 self
.mouse_zoom
= qtutils
.checkbox()
221 self
.keep_merge_backups
= qtutils
.checkbox()
222 self
.sort_bookmarks
= qtutils
.checkbox()
223 self
.save_window_settings
= qtutils
.checkbox()
224 self
.check_spelling
= qtutils
.checkbox()
225 self
.expandtab
= qtutils
.checkbox()
226 self
.resize_browser_columns
= qtutils
.checkbox(checked
=False)
228 self
.add_row(N_('Fixed-Width Font'), self
.fixed_font
)
229 self
.add_row(N_('Font Size'), self
.font_size
)
230 self
.add_row(N_('Editor'), self
.editor
)
231 self
.add_row(N_('History Browser'), self
.historybrowser
)
232 self
.add_row(N_('Blame Viewer'), self
.blameviewer
)
233 self
.add_row(N_('Diff Tool'), self
.difftool
)
234 self
.add_row(N_('Merge Tool'), self
.mergetool
)
235 self
.add_row(N_('Recent repository count'), self
.maxrecent
)
236 self
.add_row(N_('Auto-Wrap Lines'), self
.linebreak
)
237 self
.add_row(N_('Insert spaces instead of tabs'), self
.expandtab
)
238 self
.add_row(N_('Sort bookmarks alphabetically'), self
.sort_bookmarks
)
239 self
.add_row(N_('Keep *.orig Merge Backups'), self
.keep_merge_backups
)
240 self
.add_row(N_('Save GUI Settings'), self
.save_window_settings
)
241 self
.add_row(N_('Resize File Browser columns'), self
.resize_browser_columns
)
242 self
.add_row(N_('Check spelling'), self
.check_spelling
)
243 self
.add_row(N_('Ctrl+MouseWheel to Zoom'), self
.mouse_zoom
)
247 prefs
.SAVEWINDOWSETTINGS
: (
248 self
.save_window_settings
,
249 Defaults
.save_window_settings
,
251 prefs
.TABWIDTH
: (self
.tabwidth
, Defaults
.tabwidth
),
252 prefs
.EXPANDTAB
: (self
.expandtab
, Defaults
.expandtab
),
253 prefs
.TEXTWIDTH
: (self
.textwidth
, Defaults
.textwidth
),
254 prefs
.LINEBREAK
: (self
.linebreak
, Defaults
.linebreak
),
255 prefs
.MAXRECENT
: (self
.maxrecent
, Defaults
.maxrecent
),
256 prefs
.SORT_BOOKMARKS
: (self
.sort_bookmarks
, Defaults
.sort_bookmarks
),
257 prefs
.DIFFTOOL
: (self
.difftool
, Defaults
.difftool
),
258 prefs
.EDITOR
: (self
.editor
, fallback_editor()),
259 prefs
.HISTORY_BROWSER
: (
261 prefs
.default_history_browser(),
263 prefs
.BLAME_VIEWER
: (self
.blameviewer
, Defaults
.blame_viewer
),
264 prefs
.MERGE_KEEPBACKUP
: (
265 self
.keep_merge_backups
,
266 Defaults
.merge_keep_backup
,
268 prefs
.MERGETOOL
: (self
.mergetool
, Defaults
.mergetool
),
269 prefs
.RESIZE_BROWSER_COLUMNS
: (
270 self
.resize_browser_columns
,
271 Defaults
.resize_browser_columns
,
273 prefs
.SPELL_CHECK
: (self
.check_spelling
, Defaults
.spellcheck
),
274 prefs
.MOUSE_ZOOM
: (self
.mouse_zoom
, Defaults
.mouse_zoom
),
278 # pylint: disable=no-member
279 self
.fixed_font
.currentFontChanged
.connect(self
.current_font_changed
)
280 self
.font_size
.valueChanged
.connect(self
.font_size_changed
)
282 def update_from_config(self
):
283 """Update widgets to the current config values"""
284 FormWidget
.update_from_config(self
)
285 context
= self
.context
287 with qtutils
.BlockSignals(self
.fixed_font
):
288 font
= qtutils
.diff_font(context
)
289 self
.fixed_font
.setCurrentFont(font
)
291 with qtutils
.BlockSignals(self
.font_size
):
292 font_size
= font
.pointSize()
293 self
.font_size
.setValue(font_size
)
295 def font_size_changed(self
, size
):
296 font
= self
.fixed_font
.currentFont()
297 font
.setPointSize(size
)
298 cmds
.do(prefs
.SetConfig
, self
.model
, 'global', prefs
.FONTDIFF
, font
.toString())
300 def current_font_changed(self
, font
):
301 cmds
.do(prefs
.SetConfig
, self
.model
, 'global', prefs
.FONTDIFF
, font
.toString())
304 class AppearanceFormWidget(FormWidget
):
305 def __init__(self
, context
, model
, parent
):
306 FormWidget
.__init
__(self
, context
, model
, parent
)
308 self
.theme
= qtutils
.combo_mapped(themes
.options())
309 self
.icon_theme
= qtutils
.combo_mapped(icons
.icon_themes())
311 # The transform to ustr is needed because the config reader will convert
312 # "0", "1", and "2" into integers. The "1.5" value, though, is
313 # parsed as a string, so the transform is effectively a no-op.
314 self
.high_dpi
= qtutils
.combo_mapped(hidpi
.options(), transform
=ustr
)
315 self
.high_dpi
.setEnabled(hidpi
.is_supported())
316 self
.bold_headers
= qtutils
.checkbox()
317 self
.status_show_totals
= qtutils
.checkbox()
318 self
.status_indent
= qtutils
.checkbox()
319 self
.block_cursor
= qtutils
.checkbox(checked
=True)
321 self
.add_row(N_('GUI theme'), self
.theme
)
322 self
.add_row(N_('Icon theme'), self
.icon_theme
)
323 self
.add_row(N_('High DPI'), self
.high_dpi
)
324 self
.add_row(N_('Bold on dark headers instead of italic'), self
.bold_headers
)
325 self
.add_row(N_('Show file counts in Status titles'), self
.status_show_totals
)
326 self
.add_row(N_('Indent Status paths'), self
.status_indent
)
327 self
.add_row(N_('Use a block cursor in diff editors'), self
.block_cursor
)
331 prefs
.BOLD_HEADERS
: (self
.bold_headers
, Defaults
.bold_headers
),
332 prefs
.HIDPI
: (self
.high_dpi
, Defaults
.hidpi
),
333 prefs
.STATUS_SHOW_TOTALS
: (
334 self
.status_show_totals
,
335 Defaults
.status_show_totals
,
337 prefs
.STATUS_INDENT
: (self
.status_indent
, Defaults
.status_indent
),
338 prefs
.THEME
: (self
.theme
, Defaults
.theme
),
339 prefs
.ICON_THEME
: (self
.icon_theme
, Defaults
.icon_theme
),
340 prefs
.BLOCK_CURSOR
: (self
.block_cursor
, Defaults
.block_cursor
),
345 class AppearanceWidget(QtWidgets
.QWidget
):
346 def __init__(self
, form
, parent
):
347 QtWidgets
.QWidget
.__init
__(self
, parent
)
349 self
.label
= QtWidgets
.QLabel(
351 + N_('Restart the application after changing appearance settings.')
354 layout
= qtutils
.vbox(
362 self
.setLayout(layout
)
364 def update_from_config(self
):
365 self
.form
.update_from_config()
368 class PreferencesView(standard
.Dialog
):
369 def __init__(self
, context
, model
, parent
=None):
370 standard
.Dialog
.__init
__(self
, parent
=parent
)
371 self
.context
= context
372 self
.setWindowTitle(N_('Preferences'))
373 if parent
is not None:
374 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
376 self
.resize(600, 360)
378 self
.tab_bar
= QtWidgets
.QTabBar()
379 self
.tab_bar
.setDrawBase(False)
380 self
.tab_bar
.addTab(N_('All Repositories'))
381 self
.tab_bar
.addTab(N_('Current Repository'))
382 self
.tab_bar
.addTab(N_('Settings'))
383 self
.tab_bar
.addTab(N_('Appearance'))
385 self
.user_form
= RepoFormWidget(context
, model
, self
, source
='global')
386 self
.repo_form
= RepoFormWidget(context
, model
, self
, source
='local')
387 self
.options_form
= SettingsFormWidget(context
, model
, self
)
388 self
.appearance_form
= AppearanceFormWidget(context
, model
, self
)
389 self
.appearance
= AppearanceWidget(self
.appearance_form
, self
)
391 self
.stack_widget
= QtWidgets
.QStackedWidget()
392 self
.stack_widget
.addWidget(self
.user_form
)
393 self
.stack_widget
.addWidget(self
.repo_form
)
394 self
.stack_widget
.addWidget(self
.options_form
)
395 self
.stack_widget
.addWidget(self
.appearance
)
397 self
.close_button
= qtutils
.close_button()
399 self
.button_layout
= qtutils
.hbox(
400 defs
.no_margin
, defs
.spacing
, qtutils
.STRETCH
, self
.close_button
403 self
.main_layout
= qtutils
.vbox(
410 self
.setLayout(self
.main_layout
)
412 # pylint: disable=no-member
413 self
.tab_bar
.currentChanged
.connect(self
.stack_widget
.setCurrentIndex
)
414 self
.stack_widget
.currentChanged
.connect(self
.update_widget
)
416 qtutils
.connect_button(self
.close_button
, self
.accept
)
417 qtutils
.add_close_action(self
)
419 self
.update_widget(0)
421 def update_widget(self
, idx
):
422 widget
= self
.stack_widget
.widget(idx
)
423 widget
.update_from_config()