Skip to content

Commit

Permalink
[BUG] Progress bar works when reading USDZ files
Browse files Browse the repository at this point in the history
[BUG] Find case sensitivity preference saves properly
[BUG] Do not prompt to save when opening a new file
[BUG] Windows support for usdcat and unzipping USDZ
[ENH] Add line limit user preference for loading large files
[ENH] Preferences and defaults maintenance, Advanced tab added to Preferences

Version up to 0.7.0

Preferences and defaults no longer maintained in two separate places. Changing
preferences no longer force reloads your current tab. Line limit increased from
10,000 to 50,000, with a user preference added to override this.

Switch to zipfile module instead of unzip command for reading USDZ

Signed-off-by: mds-dwa <[email protected]>
  • Loading branch information
mds-dwa committed Jul 22, 2019
1 parent c81edfe commit 615e675
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 120 deletions.
127 changes: 80 additions & 47 deletions usdmanager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ class UsdMngrWindow(QtWidgets.QMainWindow):
- AddressBar file completer has problems occasionally.
- Figure out why network printers aren't showing up. Linux or DWA
issue? macOS and Windows are fine.
- When reading in a USDZ file, the progress bar gets stuck.
- Qt.py problems:
- PyQt5
Expand Down Expand Up @@ -208,7 +207,7 @@ def __init__(self, parent=None, **kwargs):
# externally. The user's preferred programs are stored in
# self.programs.
self.defaultPrograms = {x: "" for x in USD_EXTS}
self.defaultPrograms.update(self.app.appConfig.get("defaultPrograms", {}))
self.defaultPrograms.update(self.app.DEFAULTS['defaultPrograms'])
self.programs = self.defaultPrograms
self.masterHighlighters = {}

Expand Down Expand Up @@ -247,12 +246,14 @@ def setupUi(self):
self.baseInstance = utils.loadUiWidget('main_window.ui', self)

# You now have access to the widgets defined in the ui file.
self.defaultDocFont = QtGui.QFont()
self.defaultDocFont.setStyleHint(QtGui.QFont.Courier)
self.defaultDocFont.setFamily("Monospace")
self.defaultDocFont.setPointSize(9)
self.defaultDocFont.setBold(False)
self.defaultDocFont.setItalic(False)
# Update some app defaults that required the GUI to be created first.
defaultDocFont = QtGui.QFont()
defaultDocFont.setStyleHint(QtGui.QFont.Courier)
defaultDocFont.setFamily("Monospace")
defaultDocFont.setPointSize(9)
defaultDocFont.setBold(False)
defaultDocFont.setItalic(False)
self.app.DEFAULTS['font'] = defaultDocFont

self.readSettings()
self.compileLinkRegEx()
Expand Down Expand Up @@ -288,13 +289,13 @@ def setupUi(self):
</style></head><body style="white-space:pre">{}</body></html>"""

searchPaths = QtGui.QIcon.themeSearchPaths()
extraSearchPaths = [x for x in self.app.appConfig.get("themeSearchPaths", []) if x not in searchPaths]
extraSearchPaths = [x for x in self.app.DEFAULTS['themeSearchPaths'] if x not in searchPaths]
if extraSearchPaths:
searchPaths = extraSearchPaths + searchPaths
QtGui.QIcon.setThemeSearchPaths(searchPaths)

# Set the preferred theme name for some non-standard icons.
QtGui.QIcon.setThemeName(self.app.appConfig.get("iconTheme", "crystal_project"))
QtGui.QIcon.setThemeName(self.app.DEFAULTS['iconTheme'])

# Try to adhere to the freedesktop icon standards (https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html).
# Some icons are preferred from the crystal_project set, which sadly follows different naming standards.
Expand Down Expand Up @@ -633,27 +634,27 @@ def readSettings(self):
""" Read in user config settings.
"""
logger.debug("Reading user settings from {}".format(self.config.fileName()))
# Get basic preferences.
# TODO: Read some of these from the same places as the preferences dialog so we don't have to maintain defaults in 2 places.
default = self.app.DEFAULTS
self.preferences = {
'parseLinks': self.config.boolValue("parseLinks", True),
'newTab': self.config.boolValue("newTab", False),
'syntaxHighlighting': self.config.boolValue("syntaxHighlighting", True),
'teletype': self.config.boolValue("teletype", True),
'lineNumbers': self.config.boolValue("lineNumbers", True),
'showAllMessages': self.config.boolValue("showAllMessages", True),
'showHiddenFiles': self.config.boolValue("showHiddenFiles", False),
'font': self.config.value("font", self.defaultDocFont),
'fontSizeAdjust': int(self.config.value("fontSizeAdjust", 0)),
'findMatchCase': self.config.boolValue("findMatchCase", self.checkBoxMatchCase.isChecked()),
'includeVisible': self.config.boolValue("includeVisible", self.actionIncludePanel.isChecked()),
'lastOpenWithStr': self.config.value("lastOpenWithStr", ""),
'textEditor': self.config.value("textEditor", os.getenv("EDITOR", self.app.appConfig.get("textEditor", "nedit"))),
'diffTool': self.config.value("diffTool", self.app.appConfig.get("diffTool", "xdiff")),
'autoCompleteAddressBar': self.config.boolValue("autoCompleteAddressBar", True),
'useSpaces': self.config.boolValue("useSpaces", True),
'tabSpaces': int(self.config.value("tabSpaces", 4)),
'theme': self.config.value("theme", None),
'parseLinks': self.config.boolValue("parseLinks", default['parseLinks']),
'newTab': self.config.boolValue("newTab", default['newTab']),
'syntaxHighlighting': self.config.boolValue("syntaxHighlighting", default['syntaxHighlighting']),
'teletype': self.config.boolValue("teletype", default['teletype']),
'lineNumbers': self.config.boolValue("lineNumbers", default['lineNumbers']),
'showAllMessages': self.config.boolValue("showAllMessages", default['showAllMessages']),
'showHiddenFiles': self.config.boolValue("showHiddenFiles", default['showHiddenFiles']),
'font': self.config.value("font", default['font']),
'fontSizeAdjust': int(self.config.value("fontSizeAdjust", default['fontSizeAdjust'])),
'findMatchCase': self.config.boolValue("findMatchCase", default['findMatchCase']),
'includeVisible': self.config.boolValue("includeVisible", default['includeVisible']),
'lastOpenWithStr': self.config.value("lastOpenWithStr", default['lastOpenWithStr']),
'textEditor': self.config.value("textEditor", default['textEditor']),
'diffTool': self.config.value("diffTool", default['diffTool']),
'autoCompleteAddressBar': self.config.boolValue("autoCompleteAddressBar", default['autoCompleteAddressBar']),
'useSpaces': self.config.boolValue("useSpaces", default['useSpaces']),
'tabSpaces': int(self.config.value("tabSpaces", default['tabSpaces'])),
'theme': self.config.value("theme", default['theme']),
'lineLimit': int(self.config.value("lineLimit", default['lineLimit'])),
}

# Read 'programs' settings object into self.programs.
Expand Down Expand Up @@ -736,6 +737,7 @@ def writeSettings(self):
self.config.setValue("useSpaces", self.preferences['useSpaces'])
self.config.setValue("tabSpaces", self.preferences['tabSpaces'])
self.config.setValue("theme", self.preferences['theme'])
self.config.setValue("lineLimit", self.preferences['lineLimit'])

# Write self.programs to settings object
exts = self.programs.keys()
Expand Down Expand Up @@ -1918,11 +1920,14 @@ def editPreferences(self):
dlg = PreferencesDialog(self)
# Open dialog.
if dlg.exec_() == dlg.Accepted:
# Save new preferences.
# Users currently have to refresh to see these changes.
self.preferences['parseLinks'] = dlg.getPrefParseLinks()
self.preferences['newTab'] = dlg.getPrefNewTab()
self.preferences['syntaxHighlighting'] = dlg.getPrefSyntaxHighlighting()
self.preferences['teletype'] = dlg.getPrefTeletypeConversion()
self.preferences['theme'] = dlg.getPrefTheme()

# These changes do not require the user to refresh any tabs to see the change.
self.preferences['newTab'] = dlg.getPrefNewTab()
self.preferences['lineNumbers'] = dlg.getPrefLineNumbers()
self.preferences['showAllMessages'] = dlg.getPrefShowAllMessages()
self.preferences['showHiddenFiles'] = dlg.getPrefShowHiddenFiles()
Expand All @@ -1932,7 +1937,7 @@ def editPreferences(self):
self.preferences['font'] = dlg.getPrefFont()
self.preferences['useSpaces'] = dlg.getPrefUseSpaces()
self.preferences['tabSpaces'] = dlg.getPrefTabSpaces()
self.preferences['theme'] = dlg.getPrefTheme()
self.preferences['lineLimit'] = dlg.getPrefLineLimit()

# Update font and line number visibility in all tabs.
self.tabWidget.setFont(self.preferences['font'])
Expand Down Expand Up @@ -1964,9 +1969,6 @@ def editPreferences(self):
else:
self.addressBar.setCompleter(QtWidgets.QCompleter())

if not self.currTab.isDirty():
self.refreshTab()

self.writeSettings()

@Slot(int)
Expand All @@ -1977,7 +1979,7 @@ def updatePreference_findMatchCase(self, checked):
checked : `int`
State of checkbox.
"""
checked = checked & QtCore.Qt.Checked
checked = checked == QtCore.Qt.Checked
if checked != self.preferences['findMatchCase']:
self.preferences['findMatchCase'] = checked
for lang, h in self.masterHighlighters.iteritems():
Expand Down Expand Up @@ -2185,7 +2187,7 @@ def restoreTab(self, tab):
self.menuRecentlyClosedTabs.setEnabled(False)

# Update settings in the recently re-opened tab that may have changed.
if self.preferences['font'] != self.defaultDocFont:
if self.preferences['font'] != self.app.DEFAULTS['font']:
tab.textBrowser.setFont(self.preferences['font'])
tab.textEditor.setFont(self.preferences['font'])
tab.lineNumbers.setVisible(self.preferences['lineNumbers'])
Expand Down Expand Up @@ -2348,7 +2350,7 @@ def launchTextEditor(self):
def launchUsdView(self):
""" Launch the current file in usdview.
"""
app = self.app.appConfig.get("usdview", "usdview")
app = self.app.DEFAULTS['usdview']
path = self.currTab.getCurrentPath()
# Files with spaces have to be double-quoted on Windows for usdview.
if os.name == "nt":
Expand Down Expand Up @@ -2557,9 +2559,9 @@ def setSource(self, link, isNewFile=True, newTab=False, hScrollPos=0, vScrollPos
vScrollPos : `int`
Vertical scroll bar position.
"""
# Check if the current tab is dirty before doing anything.
# If we're staying in the current tab, check if the tab is dirty before doing anything.
# Perform save operations if necessary.
if not self.dirtySave():
if not newTab and not self.dirtySave():
return True

# Re-cast the QUrl so any query strings are evaluated properly.
Expand Down Expand Up @@ -2681,6 +2683,8 @@ def setSource(self, link, isNewFile=True, newTab=False, hScrollPos=0, vScrollPos
layer = utils.queryItemValue(link, "layer")
dest = utils.unzip(fileStr, layer, self.app.tmpDir)
self.restoreOverrideCursor()
self.statusbar.removeWidget(loadingProgressBar)
self.statusbar.removeWidget(loadingProgressLabel)
return self.setSource(QtCore.QUrl(dest))
else:
if ext == "usda":
Expand Down Expand Up @@ -2709,11 +2713,13 @@ def setSource(self, link, isNewFile=True, newTab=False, hScrollPos=0, vScrollPos

# TODO: Figure out a better way to handle streaming text for large files like Crate geometry.
# Large chunks of text (e.g. 2.2 billion characters) will cause Qt to segfault when creating a QString.
if length > LINE_LIMIT:
length = LINE_LIMIT
lineLimit = self.preferences['lineLimit']
if length > lineLimit:
length = lineLimit
truncated = True
fileText = fileText[:length]
warning = "Extremely large file! Capping display at {:,d} lines.".format(LINE_LIMIT)
warning = "Extremely large file! Capping display at {:,d} lines. You can edit this cap in the "\
"Advanced tab of Preferences.".format(lineLimit)

loadingProgressBar.setMaximum(length - 1)
if self.stopLoadingTab:
Expand Down Expand Up @@ -4274,13 +4280,40 @@ def run(self):
try:
logger.info("Loading app config from {}".format(appConfigPath))
with open(appConfigPath) as f:
self.appConfig = json.load(f)
appConfig = json.load(f)
except Exception as e:
logger.error("Failed to load app config from {}: {}".format(appConfigPath, e))
self.appConfig = {}
appConfig = {}

# Define app defaults that we use when the user preference doesn't exist and when resetting preferences in the
# Preferences dialog.
self.DEFAULTS = {
'autoCompleteAddressBar': True,
'defaultPrograms': appConfig.get("defaultPrograms", {}),
'diffTool': appConfig.get("diffTool", "xdiff"),
'findMatchCase': False,
'fontSizeAdjust': 0,
'iconTheme': appConfig.get("iconTheme", "crystal_project"),
'includeVisible': True,
'lastOpenWithStr': "",
'lineLimit': LINE_LIMIT,
'lineNumbers': True,
'newTab': False,
'parseLinks': True,
'showAllMessages': True,
'showHiddenFiles': False,
'syntaxHighlighting': True,
'tabSpaces': 4,
'teletype': True,
'textEditor': os.getenv("EDITOR", appConfig.get("textEditor", "nedit")),
'theme': None,
'themeSearchPaths': appConfig.get("themeSearchPaths", []),
'usdview': appConfig.get("usdview", "usdview"),
'useSpaces': True,
}

# Documentation URL.
self.appURL = self.appConfig.get("appURL", "https://github.com/dreamworksanimation/usdmanager")
self.appURL = appConfig.get("appURL", "https://github.com/dreamworksanimation/usdmanager")

# Create a main window.
window = self.newWindow()
Expand Down
3 changes: 2 additions & 1 deletion usdmanager/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@

# Truncate loading files with more lines than this.
# Display can slow down and/or become unusable with too many lines.
LINE_LIMIT = 10000
# This number is less important than the total number of characters and can be overridden in Preferences.
LINE_LIMIT = 50000

# Truncate loading files with more total chars than this.
# QString crashes at ~2.1 billion chars, but display slows down way before that.
Expand Down
40 changes: 26 additions & 14 deletions usdmanager/preferences_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from Qt.QtGui import QIcon, QRegExpValidator
from Qt.QtWidgets import (QAbstractButton, QDialog, QDialogButtonBox, QFontDialog, QLineEdit, QMessageBox, QVBoxLayout)

from .constants import LINE_LIMIT
from .utils import loadUiWidget


Expand Down Expand Up @@ -77,6 +78,7 @@ def setupUi(self, widget):
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.updateFontLabel()

# ----- Programs tab -----
Expand Down Expand Up @@ -189,6 +191,15 @@ def getPrefAutoCompleteAddressBar(self):
"""
return self.checkBox_autoCompleteAddressBar.isChecked()

def getPrefLineLimit(self):
"""
:Returns:
Number of lines to display before truncating a file.
:Rtype:
`int`
"""
return self.lineLimitSpinBox.value()

def getPrefSyntaxHighlighting(self):
"""
:Returns:
Expand Down Expand Up @@ -319,22 +330,23 @@ def restoreDefaults(self, btn):
self.deleteItems(self.extLayout)

# Set other preferences in the GUI.
window = self.parent().window()
self.checkBox_parseLinks.setChecked(True)
self.checkBox_newTab.setChecked(False)
self.checkBox_syntaxHighlighting.setChecked(True)
self.checkBox_teletypeConversion.setChecked(True)
self.checkBox_lineNumbers.setChecked(True)
self.checkBox_showAllMessages.setChecked(True)
self.checkBox_showHiddenFiles.setChecked(False)
self.checkBox_autoCompleteAddressBar.setChecked(True)
self.lineEditTextEditor.setText(os.getenv("EDITOR", window.app.appConfig.get("textEditor", "nedit")))
self.lineEditDiffTool.setText(window.app.appConfig.get("diffTool", "xdiff"))
self.useSpacesCheckBox.setChecked(True)
self.useSpacesSpinBox.setValue(4)
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 = window.defaultDocFont
self.docFont = default['font']
self.updateFontLabel()
self.lineLimitSpinBox.setValue(default['lineLimit'])

# Re-create file association fields with the default programs.
self.populateProgsAndExts(self.parent().defaultPrograms)
Expand Down
Loading

0 comments on commit 615e675

Please sign in to comment.