#
# Copyright 2018 DreamWorks Animation L.L.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" Create a Preferences dialog.
"""
from Qt.QtCore import Slot, QRegExp
from Qt.QtGui import QRegExpValidator
from Qt.QtWidgets import QAbstractButton, QDialog, QDialogButtonBox, QFontDialog, QLineEdit, QMessageBox, QVBoxLayout
from .constants import LINE_LIMIT
from .utils import icon, loadUiWidget
[docs]class PreferencesDialog(QDialog):
"""
Preferences dialog
"""
def __init__(self, parent, **kwargs):
""" Initialize the dialog.
:Parameters:
parent : `UsdMngrWindow`
Main window
"""
super(PreferencesDialog, self).__init__(parent, **kwargs)
self.docFont = parent.tabWidget.font()
self.fileAssociations = {}
self.lineEditProgs = []
self.lineEditExts = []
self.setupUi()
self.connectSignals()
[docs] def setupUi(self):
""" Creates and lays out the widgets defined in the ui file.
"""
self.baseInstance = loadUiWidget("preferences_dialog.ui", self)
self.setWindowIcon(icon("preferences-system"))
self.buttonFont.setIcon(icon("preferences-desktop-font"))
self.buttonNewProg.setIcon(icon("list-add"))
# ----- General tab -----
# Set initial preferences.
parent = self.parent()
self.checkBox_parseLinks.setChecked(parent.preferences['parseLinks'])
self.checkBox_newTab.setChecked(parent.preferences['newTab'])
self.checkBox_syntaxHighlighting.setChecked(parent.preferences['syntaxHighlighting'])
self.checkBox_teletypeConversion.setChecked(parent.preferences['teletype'])
self.checkBox_lineNumbers.setChecked(parent.preferences['lineNumbers'])
self.checkBox_showAllMessages.setChecked(parent.preferences['showAllMessages'])
self.checkBox_showHiddenFiles.setChecked(parent.preferences['showHiddenFiles'])
self.checkBox_autoCompleteAddressBar.setChecked(parent.preferences['autoCompleteAddressBar'])
self.useSpacesCheckBox.setChecked(parent.preferences['useSpaces'])
self.useSpacesSpinBox.setValue(parent.preferences['tabSpaces'])
self.lineEditTextEditor.setText(parent.preferences['textEditor'])
self.lineEditDiffTool.setText(parent.preferences['diffTool'])
self.themeWidget.setChecked(parent.preferences['theme'] == "dark")
self.lineLimitSpinBox.setValue(parent.preferences['lineLimit'])
self.checkBox_autoIndent.setChecked(parent.preferences['autoIndent'])
self.updateFontLabel()
# ----- Programs tab -----
self.progLayout = QVBoxLayout()
self.extLayout = QVBoxLayout()
# Extensions can only be: <optional .><alphanumeric><optional comma><optional space>
#self.progValidator = QRegExpValidator(QRegExp("[\w,. ]+"), self)
self.extValidator = QRegExpValidator(QRegExp(r"(?:\.?\w*,?\s*)+"), self)
self.lineEdit.setValidator(self.extValidator)
# Create the fields for programs and extensions.
self.populateProgsAndExts(parent.programs)
[docs] def connectSignals(self):
""" Connect signals to slots.
"""
self.buttonBox.clicked.connect(self.restoreDefaults)
self.buttonNewProg.clicked.connect(self.newProgField)
self.buttonBox.accepted.connect(self.validate)
self.buttonFont.clicked.connect(self.selectFont)
[docs] def deleteItems(self, layout):
""" Delete all items in given layout.
:Parameters:
layout : `QLayout`
Layout to delete items from
"""
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.deleteItems(item.layout())
[docs] def getPrefFont(self):
""" Get the user preference for font.
:Returns:
Font selected for documents.
:Rtype:
`QFont`
"""
return self.docFont
[docs] def getPrefLineNumbers(self):
""" Get the user preference for displaying line numbers.
:Returns:
State of "Show line numbers" check box.
:Rtype:
`bool`
"""
return self.checkBox_lineNumbers.isChecked()
[docs] def getPrefNewTab(self):
""" Get the user preference for opening links in a new tab or not.
:Returns:
State of "Open links in new tabs" check box.
:Rtype:
`bool`
"""
return self.checkBox_newTab.isChecked()
[docs] def getPrefParseLinks(self):
""" Get the user preference to enable link parsing.
:Returns:
Search for links in the opened file.
Disable this for huge files that freeze the app.
:Rtype:
`bool`
"""
return self.checkBox_parseLinks.isChecked()
[docs] def getPrefPrograms(self):
""" Get the user preference for file extensions and apps to open them with.
:Returns:
Dictionary of extension: program pairs of strings.
:Rtype:
`dict`
"""
return self.fileAssociations
[docs] def getPrefShowAllMessages(self):
""" Get the user preference to display all messages or just errors.
:Returns:
State of "Show success messages" check box.
:Rtype:
`bool`
"""
return self.checkBox_showAllMessages.isChecked()
[docs] def getPrefShowHiddenFiles(self):
""" Get the user preference for showing hidden files by default.
:Returns:
State of "Show hidden files" check box.
:Rtype:
`bool`
"""
return self.checkBox_showHiddenFiles.isChecked()
[docs] def getPrefAutoCompleteAddressBar(self):
""" Get the user preference for enabling address bar auto-completion.
:Returns:
State of "Auto complete paths in address bar" check box.
:Rtype:
`bool`
"""
return self.checkBox_autoCompleteAddressBar.isChecked()
[docs] def getPrefLineLimit(self):
""" Get the user preference for line limit before truncating files.
:Returns:
Number of lines to display before truncating a file.
:Rtype:
`int`
"""
return self.lineLimitSpinBox.value()
[docs] def getPrefSyntaxHighlighting(self):
""" Get the user preference to enable syntax highlighting.
:Returns:
State of "Enable syntax highlighting" check box.
:Rtype:
`bool`
"""
return self.checkBox_syntaxHighlighting.isChecked()
[docs] def getPrefTeletypeConversion(self):
""" Get the user preference to enable teletype character conversion.
:Returns:
State of "Display teletype colors" check box.
:Rtype:
`bool`
"""
return self.checkBox_teletypeConversion.isChecked()
[docs] def getPrefTextEditor(self):
""" Get the user-preferred text editor.
:Returns:
Text in Text editor QTextEdit.
:Rtype:
`str`
"""
return self.lineEditTextEditor.text()
[docs] def getPrefTheme(self):
""" Get the selected theme.
We may eventually make this a combo box supporting multiple themes,
so use the string name instead of just a boolean.
:Returns:
Selected theme name, or None if the default
:Rtype:
`str` | None
"""
return "dark" if self.themeWidget.isChecked() else None
[docs] def getPrefUseSpaces(self):
""" Get the user preference for spaces vs. tabs.
:Returns:
State of "Use spaces instead of tabs" check box.
:Rtype:
`bool`
"""
return self.useSpacesCheckBox.isChecked()
[docs] def getPrefTabSpaces(self):
""" Get the user preference for number of spaces equaling a tab.
:Returns:
Number of spaces to use instead of a tab.
Only use this number of use spaces is also True.
:Rtype:
`int`
"""
return self.useSpacesSpinBox.value()
[docs] def getPrefAutoIndent(self):
""" Get the user preference for auto-indentation.
:Returns:
State of "Use auto indentation" check box.
:Rtype:
`bool`
"""
return self.checkBox_autoIndent.isChecked()
[docs] @Slot(bool)
def newProgField(self, *args):
""" Add a new line to the programs list.
"""
self.lineEditProgs.append(QLineEdit(self))
self.progLayout.addWidget(self.lineEditProgs[len(self.lineEditProgs)-1])
self.lineEditExts.append(QLineEdit(self))
self.extLayout.addWidget(self.lineEditExts[len(self.lineEditExts)-1])
[docs] def populateProgsAndExts(self, programs):
""" Fill out the UI with the user preference for programs and extensions.
:Parameters:
programs : `dict`
Dictionary of extension: program pairs of strings.
"""
self.lineEditProgs = []
self.lineEditExts = []
# Get unique programs.
tmpSet = set()
progs = [x for x in programs.values() if x not in tmpSet and not tmpSet.add(x)]
del tmpSet
progs.sort()
# Get extensions per program.
exts = []
for prog in progs:
# Find each extension matching this program.
progExts = ["."+x for x in programs if programs[x] == prog]
progExts.sort()
# Format in comma-separated list for display.
exts.append(", ".join(progExts))
# Put the files that should open with this app in their own place.
# Then remove them from these lists.
index = progs.index("")
progs.pop(index)
self.lineEdit.setText(exts[index])
exts.pop(index)
del index
for i, prog in enumerate(progs):
# Create and populate two QLineEdit objects per extension: program pair.
self.lineEditProgs.append(QLineEdit(prog, self))
#self.lineEditProgs[i].setValidator(self.progValidator)
self.progLayout.addWidget(self.lineEditProgs[i])
self.lineEditExts.append(QLineEdit(exts[i], self))
self.lineEditExts[i].setValidator(self.extValidator)
self.extLayout.addWidget(self.lineEditExts[i])
self.progWidget.setLayout(self.progLayout)
self.extWidget.setLayout(self.extLayout)
[docs] @Slot(QAbstractButton)
def restoreDefaults(self, btn):
""" Restore the GUI to the program's default settings.
Don't update the actual preferences (that happens if OK is pressed).
"""
if btn == self.buttonBox.button(QDialogButtonBox.RestoreDefaults):
# Delete old QLineEdit objects.
self.deleteItems(self.progLayout)
self.deleteItems(self.extLayout)
# Set other preferences in the GUI.
default = self.parent().window().app.DEFAULTS
self.checkBox_parseLinks.setChecked(default['parseLinks'])
self.checkBox_newTab.setChecked(default['newTab'])
self.checkBox_syntaxHighlighting.setChecked(default['syntaxHighlighting'])
self.checkBox_teletypeConversion.setChecked(default['teletype'])
self.checkBox_lineNumbers.setChecked(default['lineNumbers'])
self.checkBox_showAllMessages.setChecked(default['showAllMessages'])
self.checkBox_showHiddenFiles.setChecked(default['showHiddenFiles'])
self.checkBox_autoCompleteAddressBar.setChecked(default['autoCompleteAddressBar'])
self.lineEditTextEditor.setText(default['textEditor'])
self.lineEditDiffTool.setText(default['diffTool'])
self.useSpacesCheckBox.setChecked(default['useSpaces'])
self.useSpacesSpinBox.setValue(default['tabSpaces'])
self.themeWidget.setChecked(False)
self.docFont = default['font']
self.updateFontLabel()
self.lineLimitSpinBox.setValue(default['lineLimit'])
self.checkBox_autoIndent.setChecked(default['autoIndent'])
# Re-create file association fields with the default programs.
self.populateProgsAndExts(self.parent().defaultPrograms)
[docs] @Slot(bool)
def selectFont(self, *args):
""" Update the user's font preference.
"""
font, ok = QFontDialog.getFont(self.docFont, self, "Select Font")
if ok:
self.docFont = font
self.updateFontLabel()
[docs] def updateFontLabel(self):
""" Update the UI font label to show the user's selected font.
"""
bold = "Bold " if self.docFont.bold() else ""
italic = "Italic " if self.docFont.italic() else ""
self.labelFont.setText("Document font: {}pt {}{}{}".format(self.docFont.pointSize(), bold, italic,
self.docFont.family()))
[docs] @Slot()
def validate(self):
""" Make sure everything has valid input.
Make sure there are no duplicate extensions.
Accepts or rejects accepted() signal accordingly.
"""
for lineEdit in self.lineEditExts:
if lineEdit.hasAcceptableInput():
lineEdit.setStyleSheet("background-color:none")
else:
lineEdit.setStyleSheet("background-color:salmon")
QMessageBox.warning(self, "Warning", "One or more extension is invalid.")
return
# Get file extensions for this app to handle.
extText = self.lineEdit.text()
# Strip out periods and spaces.
extText = extText.replace(' ', '').replace('.', '')
progList = [[x, ""] for x in extText.split(',') if x]
for i in range(len(self.lineEditProgs)):
extText = self.lineEditExts[i].text()
progText = self.lineEditProgs[i].text()
extText = extText.replace(' ', '').replace('.', '')
for ext in extText.split(','):
if ext:
progList.append([ext, progText])
# Make sure there aren't any duplicate extensions.
tmpSet = set()
uniqueExt = [ext for ext, prog in progList if ext not in tmpSet and not tmpSet.add(ext)]
if len(uniqueExt) == len(progList):
self.fileAssociations = dict(progList)
else:
QMessageBox.warning(self, "Warning", "You have entered the same extension for two or more programs.")
return
# Accept if we made it this far.
self.accept()