Skip to content

Commit

Permalink
[REL] 0.15.0
Browse files Browse the repository at this point in the history
[ENH] add parser-specific Commands menu items
[ENH] support string substitution for more complex config subprocess commands
[ENH] select lines via clicking line numbers
[BUG] enable "Copy link location" context menu action
[BUG] Save As should add file to recent files and history menus
[BUG] improve PySide2/PyQt5 support
[MNT] remove no longer necessary utils.loadUiType
[MNT] safer subprocess usage, removing all shell=True usage
[MNT] add missing docstrings

Signed-off-by: mds-dwa <[email protected]>
  • Loading branch information
mds-dwa committed Jul 18, 2022
1 parent a386c44 commit f5d3ee8
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 338 deletions.
13 changes: 12 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ Example app config JSON file:
}
```

## Language-specific parsing

When default path parsing logic is not good enough, you can add unique parsing for file types by subclassing the
[parser.AbstractExtParser](https://github.com/dreamworksanimation/usdmanager/blob/master/usdmanager/parser.py).
Save your new class in a file in the parsers directory. Set the class's extensions tuple (e.g. (".html", ".xml")) for a
simple list of file extensions to match, or override the acceptsFile method for more advanced control.

Within each parser, you can define custom menu items that will be added to the bottom of the Commands menu whenever a
parser is active. For example, the [USD parser](https://github.com/dreamworksanimation/usdmanager/blob/master/usdmanager/parsers/usd.py)
adds an "Open in usdview..." action.

## Syntax highlighting

To add syntax highlighting for additional languages, subclass the
Expand Down Expand Up @@ -122,4 +133,4 @@ following to update [usdmanager/plugins/images_rc.py](https://github.com/dreamwo
pyrcc4 usdmanager/plugins/images.rc > usdmanager/plugins/images_rc.py
```

If using pyrcc4, be sure to replace PyQt4 with Qt in the images_rc.py's import line.
If using pyrcc4, be sure to replace PyQt4 with Qt in the images_rc.py's import line.
278 changes: 178 additions & 100 deletions usdmanager/__init__.py

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions usdmanager/file_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def __init__(self, parent=None, caption="", directory="", filters=None, selected
Show hidden files
"""
super(FileDialog, self).__init__(parent, caption, directory, ';;'.join(filters or FILE_FILTER))

# The following line avoids this warning with Qt5:
# "GtkDialog mapped without a transient parent. This is discouraged."
self.setOption(QFileDialog.DontUseNativeDialog)

if selectedFilter:
self.selectNameFilter(selectedFilter)
if showHidden:
Expand Down
44 changes: 17 additions & 27 deletions usdmanager/find_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,54 +13,44 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" Create the Find or Find/Replace dialog.
"""
from Qt.QtCore import Slot
from Qt.QtWidgets import QStatusBar
from Qt.QtWidgets import QDialog, QStatusBar
from Qt.QtGui import QIcon, QTextDocument

from .utils import loadUiType
from .utils import loadUiWidget

try:
UI_TYPE = loadUiType("find_dialog.ui")
except KeyError:
# Building docs, have a safe fallback
from Qt.QtWidgets import QDialog
UI_TYPE = QDialog

class FindDialog(UI_TYPE):
class FindDialog(QDialog):
"""
Find/Replace dialog
"""
def __init__(self, parent=None, **kwargs):
""" Initialize the dialog.
:Parameters:
parent : `QtWidgets.QWidget` | None
Parent widget
"""
super(FindDialog, self).__init__(parent, **kwargs)
self.setupUi(self)
self.setupUi()
self.connectSignals()

def setupUi(self, widget):
"""
Creates and lays out the widgets defined in the ui file.
:Parameters:
widget : `QtWidgets.QWidget`
Base widget

def setupUi(self):
""" Creates and lays out the widgets defined in the ui file.
"""
super(FindDialog, self).setupUi(widget)
self.baseInstance = loadUiWidget('find_dialog.ui', self)
self.statusBar = QStatusBar(self)
self.verticalLayout.addWidget(self.statusBar)
self.findBtn.setIcon(QIcon.fromTheme("edit-find"))
self.replaceBtn.setIcon(QIcon.fromTheme("edit-find-replace"))

def connectSignals(self):
"""
Connect signals to slots.
""" Connect signals to slots.
"""
self.findLineEdit.textChanged.connect(self.updateButtons)

def searchFlags(self):
""" Get find flags based on checked options.
Expand All @@ -82,7 +72,7 @@ def searchFlags(self):
def updateButtons(self, text):
"""
Update enabled state of buttons as entered text changes.
:Parameters:
text : `str`
Currently entered find text
Expand All @@ -96,13 +86,13 @@ def updateButtons(self, text):
if not enabled:
self.statusBar.clearMessage()
self.setStyleSheet("QLineEdit#findLineEdit{background:none}")

@Slot(bool)
def updateForEditMode(self, edit):
"""
Show/Hide text replacement options based on if we are editing or not.
If editing, allow replacement of the found text.
:Parameters:
edit : `bool`
If in edit mode or not
Expand Down
100 changes: 80 additions & 20 deletions usdmanager/linenumbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PlainTextLineNumbers(QWidget):
"""
def __init__(self, parent):
""" Initialize the line numbers widget.
:Parameters:
parent : `QPlainTextEdit`
Text widget
Expand All @@ -42,17 +42,18 @@ def __init__(self, parent):
self.textWidget = parent
self._hiddenByUser = False
self._highlightCurrentLine = True
self._movePos = None

# Monospaced font to keep width from shifting.
font = QFont()
font.setStyleHint(QFont.Courier)
font.setFamily("Monospace")
self.setFont(font)

self.connectSignals()
self.updateLineWidth()
self.highlightCurrentLine()

def blockCount(self):
return self.textWidget.blockCount()

Expand All @@ -74,7 +75,7 @@ def highlightCurrentLine(self):
"""
if not self._highlightCurrentLine:
return False

extras = [x for x in self.textWidget.extraSelections() if x.format.property(QTextFormat.UserProperty) != "line"]
selection = QTextEdit.ExtraSelection()
lineColor = QColor(QtCore.Qt.darkGray).darker() if self.window().isDarkTheme() else \
Expand All @@ -96,7 +97,7 @@ def highlightCurrentLine(self):
'''
self.textWidget.setExtraSelections(extras)
return True

def lineWidth(self, count=0):
""" Calculate the width of the widget based on the block count.
Expand All @@ -114,6 +115,64 @@ def lineWidth(self, count=0):
# Obsolete in Qt 5.
return 6 + self.fontMetrics().width(blocks)

def mouseMoveEvent(self, event):
""" Track mouse movement to select more lines if press is active.
:Parameters:
event : `QMouseEvent`
Mouse move event
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.accept()
return

cursor = self.textWidget.textCursor()
cursor2 = self.textWidget.cursorForPosition(event.pos())
new = cursor2.position()
if new == self._movePos:
event.accept()
return

cursor.setPosition(self._movePos)
if new > self._movePos:
cursor.movePosition(cursor.StartOfLine)
cursor2.movePosition(cursor2.EndOfLine)
else:
cursor.movePosition(cursor.EndOfLine)
cursor2.movePosition(cursor2.StartOfLine)
cursor.setPosition(cursor2.position(), cursor.KeepAnchor)
self.textWidget.setTextCursor(cursor)
event.accept()

def mousePressEvent(self, event):
""" Select the line that was clicked. If moved while pressed, select
multiple lines as the mouse moves.
:Parameters:
event : `QMouseEvent`
Mouse press event
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.accept()
return

cursor = self.textWidget.cursorForPosition(event.pos())
cursor.select(cursor.LineUnderCursor)

# Allow Shift-selecting lines from the previous selection to new position.
if self.textWidget.textCursor().hasSelection() and event.modifiers() == QtCore.Qt.ShiftModifier:
cursor2 = self.textWidget.textCursor()
self._movePos = cursor2.position()
start = min(cursor.selectionStart(), cursor2.selectionStart())
end = max(cursor.selectionEnd(), cursor2.selectionEnd())
cursor.setPosition(start)
cursor.setPosition(end, cursor.KeepAnchor)
else:
self._movePos = cursor.position()

self.textWidget.setTextCursor(cursor)
event.accept()

def onEditorResize(self):
""" Adjust line numbers size if the text widget is resized.
"""
Expand Down Expand Up @@ -157,15 +216,15 @@ def paintEvent(self, event):
top = bottom
bottom = top + round(textWidget.blockBoundingRect(block).height())
blockNumber += 1

def setVisible(self, visible):
super(PlainTextLineNumbers, self).setVisible(visible)
self._hiddenByUser = not visible
self.updateLineWidth()

def sizeHint(self):
return QSize(self.lineWidth(), 0)
return QSize(self.lineWidth(), self.textWidget.height())

@Slot(QRect, int)
def updateLineNumbers(self, rect, dY):
""" Scroll the line numbers or repaint the visible numbers.
Expand All @@ -176,7 +235,7 @@ def updateLineNumbers(self, rect, dY):
self.update(0, rect.y(), self.width(), rect.height())
if rect.contains(self.textWidget.viewport().rect()):
self.updateLineWidth()

@Slot(int)
def updateLineWidth(self, count=0):
""" Adjust display of text widget to account for the widget of the line numbers.
Expand All @@ -203,34 +262,34 @@ def connectSignals(self):
self.textWidget.currentCharFormatChanged.connect(self.resizeAndUpdate)
self.textWidget.cursorPositionChanged.connect(self.highlightCurrentLine)
self.doc.blockCountChanged.connect(self.updateLineWidth)

@Slot()
def highlightCurrentLine(self):
""" Make sure the active line number is redrawn in bold by calling update.
"""
if super(LineNumbers, self).highlightCurrentLine():
self.update()

@Slot(QTextCharFormat)
def resizeAndUpdate(self, *args):
""" Resize bar if needed.
"""
self.updateLineWidth()
super(LineNumbers, self).update()

def paintEvent(self, event):
""" Draw line numbers.
"""
painter = QPainter(self)
painter.fillRect(event.rect(), QColor(QtCore.Qt.darkGray).darker(300) if self.window().isDarkTheme() \
else QtCore.Qt.lightGray)

textWidget = self.textWidget
doc = textWidget.document()
vScrollPos = textWidget.verticalScrollBar().value()
pageBtm = vScrollPos + textWidget.viewport().height()
currBlock = doc.findBlock(textWidget.textCursor().position())

width = self.width() - 3 # 3 is magic padding number
height = textWidget.fontMetrics().height()
flags = QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
Expand All @@ -244,19 +303,20 @@ def paintEvent(self, event):

# Find roughly the current top-most visible block.
block = doc.begin()
lineHeight = doc.documentLayout().blockBoundingRect(block).height()

layout = doc.documentLayout()
lineHeight = layout.blockBoundingRect(block).height()

block = doc.findBlockByNumber(int(vScrollPos / lineHeight))
currLine = block.blockNumber()

while block.isValid():
currLine += 1

# Check if the position of the block is outside the visible area.
yPos = doc.documentLayout().blockBoundingRect(block).topLeft().y()
yPos = layout.blockBoundingRect(block).topLeft().y()
if yPos > pageBtm:
break

# Make the line number for the selected line bold.
font.setBold(block == currBlock)
painter.setFont(font)
Expand Down
14 changes: 1 addition & 13 deletions usdmanager/main_window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@
<addaction name="separator"/>
<addaction name="actionOpenWith"/>
<addaction name="actionTextEditor"/>
<addaction name="actionUsdView"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
Expand Down Expand Up @@ -996,18 +996,6 @@
<string>Open files in new tabs</string>
</property>
</action>
<action name="actionUsdView">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="images.qrc">
<normaloff>:/images/images/usd.png</normaloff>:/images/images/usd.png</iconset>
</property>
<property name="text">
<string>Open with usd&amp;view...</string>
</property>
</action>
<action name="actionTextEditor">
<property name="enabled">
<bool>false</bool>
Expand Down
Loading

0 comments on commit f5d3ee8

Please sign in to comment.