1#
2# Copyright 2016 Pixar
3#
4# Licensed under the Apache License, Version 2.0 (the "Apache License")
5# with the following modification; you may not use this file except in
6# compliance with the Apache License and the following modification to it:
7# Section 6. Trademarks. is deleted and replaced with:
8#
9# 6. Trademarks. This License does not grant permission to use the trade
10#    names, trademarks, service marks, or product names of the Licensor
11#    and its affiliates, except as required to comply with Section 4(c) of
12#    the License and to reproduce the content of the NOTICE file.
13#
14# You may obtain a copy of the Apache License at
15#
16#     http://www.apache.org/licenses/LICENSE-2.0
17#
18# Unless required by applicable law or agreed to in writing, software
19# distributed under the Apache License with the above modification is
20# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21# KIND, either express or implied. See the Apache License for the specific
22# language governing permissions and limitations under the Apache License.
23#
24from __future__ import print_function
25
26from pxr import Tf
27
28from .common import DefaultFontFamily
29from .qt import QtCore, QtGui, QtWidgets
30from .usdviewApi import UsdviewApi
31
32from code import InteractiveInterpreter
33import os, sys, keyword
34
35# just a handy debugging method
36def _PrintToErr(line):
37    old = sys.stdout
38    sys.stdout = sys.__stderr__
39    print(line)
40    sys.stdout = old
41
42def _Redirected(method):
43    def new(self, *args, **kw):
44        old = sys.stdin, sys.stdout, sys.stderr
45        sys.stdin, sys.stdout, sys.stderr = self, self, self
46        try:
47            ret = method(self, *args, **kw)
48        finally:
49            sys.stdin, sys.stdout, sys.stderr = old
50        return ret
51    return new
52
53class _Completer(object):
54    """Taken from rlcompleter, with readline references stripped, and a local
55    dictionary to use."""
56
57    def __init__(self, locals):
58        self.locals = locals
59
60    def Complete(self, text, state):
61        """Return the next possible completion for 'text'.
62        This is called successively with state == 0, 1, 2, ... until it
63        returns None.  The completion should begin with 'text'.
64        """
65        if state == 0:
66            if "." in text:
67                self.matches = self._AttrMatches(text)
68            else:
69                self.matches = self._GlobalMatches(text)
70        try:
71            return self.matches[state]
72        except IndexError:
73            return None
74
75    def _GlobalMatches(self, text):
76        """Compute matches when text is a simple name.
77
78        Return a list of all keywords, built-in functions and names
79        currently defines in __main__ that match.
80        """
81        builtin_mod = None
82
83        if sys.version_info.major >= 3:
84            import builtins
85            builtin_mod = builtins
86        else:
87            import __builtin__
88            builtin_mod = __builtin__
89
90        import __main__
91
92        matches = set()
93        n = len(text)
94        for l in [keyword.kwlist,builtin_mod.__dict__.keys(),
95                  __main__.__dict__.keys(), self.locals.keys()]:
96            for word in l:
97                if word[:n] == text and word != "__builtins__":
98                    matches.add(word)
99        return list(matches)
100
101    def _AttrMatches(self, text):
102        """Compute matches when text contains a dot.
103
104        Assuming the text is of the form NAME.NAME....[NAME], and is
105        evaluatable in the globals of __main__, it will be evaluated
106        and its attributes (as revealed by dir()) are used as possible
107        completions.  (For class instances, class members are are also
108        considered.)
109
110        WARNING: this can still invoke arbitrary C code, if an object
111        with a __getattr__ hook is evaluated.
112        """
113        import re, __main__
114
115        assert len(text)
116
117        # This is all a bit hacky, but that's tab-completion for you.
118
119        # Now find the last index in the text of a set of characters, and split
120        # the string into a prefix and suffix token there.  The suffix token
121        # will be used for completion.
122        splitChars = ' )(;,+=*/-%!<>'
123        index = -1
124        for char in splitChars:
125            index = max(text.rfind(char), index)
126
127        if index >= len(text)-1:
128            return []
129
130        prefix = ''
131        suffix = text
132        if index >= 0:
133            prefix = text[:index+1]
134            suffix = text[index+1:]
135
136        m = re.match(r"([^.]+(\.[^.]+)*)\.(.*)", suffix)
137        if not m:
138            return []
139        expr, attr = m.group(1, 3)
140
141        try:
142            myobject = eval(expr, __main__.__dict__, self.locals)
143        except (AttributeError, NameError, SyntaxError):
144            return []
145
146        words = set(dir(myobject))
147        if hasattr(myobject,'__class__'):
148            words.add('__class__')
149        words = words.union(set(_GetClassMembers(myobject.__class__)))
150
151        words = list(words)
152        matches = set()
153        n = len(attr)
154        for word in words:
155            if word[:n] == attr and word != "__builtins__":
156                matches.add("%s%s.%s" % (prefix, expr, word))
157        return list(matches)
158
159def _GetClassMembers(cls):
160    ret = dir(cls)
161    if hasattr(cls, '__bases__'):
162        for base in cls.__bases__:
163            ret = ret + _GetClassMembers(base)
164    return ret
165
166
167class Interpreter(InteractiveInterpreter):
168    def __init__(self, locals = None):
169        InteractiveInterpreter.__init__(self,locals)
170        self._outputBrush = None
171
172    # overridden
173    def showsyntaxerror(self, filename = None):
174        self._outputBrush = QtGui.QBrush(QtGui.QColor('#ffcc63'))
175
176        try:
177            InteractiveInterpreter.showsyntaxerror(self, filename)
178        finally:
179            self._outputBrush = None
180
181    # overridden
182    def showtraceback(self):
183        self._outputBrush = QtGui.QBrush(QtGui.QColor('#ff0000'))
184
185        try:
186            InteractiveInterpreter.showtraceback(self)
187        finally:
188            self._outputBrush = None
189
190    def GetOutputBrush(self):
191        return self._outputBrush
192
193# Modified from site.py in the Python distribution.
194#
195# This allows each interpreter editor to have it's own Helper object.
196# The built-in pydoc.help grabs sys.stdin and sys.stdout the first time it
197# is run and then never lets them go.
198class _Helper(object):
199    """Define a replacement for the built-in 'help'.
200    This is a wrapper around pydoc.Helper (with a twist).
201
202    """
203    def __init__(self, input, output):
204        import pydoc
205        self._helper = pydoc.Helper(input, output)
206
207    def __repr__(self):
208        return "Type help() for interactive help, " \
209               "or help(object) for help about object."
210
211    def __call__(self, *args, **kwds):
212        return self._helper(*args, **kwds)
213
214
215class Controller(QtCore.QObject):
216    """
217    Controller is a Python shell written using Qt.
218
219    This class is a controller between Python and something which acts
220    like a QTextEdit.
221
222    """
223
224    _isAnyReadlineEventLoopActive = False
225
226    def __init__(self, textEdit, initialPrompt, locals = None):
227        """Constructor.
228
229        The optional 'locals' argument specifies the dictionary in
230        which code will be executed; it defaults to a newly created
231        dictionary with key "__name__" set to "__console__" and key
232        "__doc__" set to None.
233
234        """
235
236        super(Controller, self).__init__()
237
238        self.interpreter = Interpreter(locals)
239        self.interpreter.locals['help'] = _Helper(self, self)
240
241        self.completer = _Completer(self.interpreter.locals)
242        # last line + last incomplete lines
243        self.lines = []
244
245        # flag: the interpreter needs more input to run the last lines.
246        self.more = 0
247
248        # history
249        self.history = []
250        self.historyPointer = None
251        self.historyInput = ''
252
253        # flag: readline() is being used for e.g. raw_input and input().
254        # We use a nested QEventloop here because we want to emulate
255        # modeless UI even though the readline protocol requires blocking calls.
256        self.readlineEventLoop = QtCore.QEventLoop(textEdit)
257
258        # interpreter prompt.
259        try:
260            sys.ps1
261        except AttributeError:
262            sys.ps1 = ">>> "
263        try:
264            sys.ps2
265        except AttributeError:
266            sys.ps2 = "... "
267
268        self.textEdit = textEdit
269        self.textEdit.destroyed.connect(self._TextEditDestroyedSlot)
270
271        self.textEdit.returnPressed.connect(self._ReturnPressedSlot)
272
273        self.textEdit.requestComplete.connect(self._CompleteSlot)
274
275        self.textEdit.requestNext.connect(self._NextSlot)
276
277        self.textEdit.requestPrev.connect(self._PrevSlot)
278
279        appInstance = QtWidgets.QApplication.instance()
280        appInstance.aboutToQuit.connect(self._QuitSlot)
281
282        self.textEdit.setTabChangesFocus(False)
283
284        self.textEdit.setWordWrapMode(QtGui.QTextOption.WrapAnywhere)
285        self.textEdit.setWindowTitle('Interpreter')
286
287        self.textEdit.promptLength = len(sys.ps1)
288
289        # Do initial auto-import.
290        self._DoAutoImports()
291
292        # interpreter banner
293        self.write('Python %s on %s.\n' % (sys.version, sys.platform))
294
295        # Run $PYTHONSTARTUP startup script.
296        startupFile = os.getenv('PYTHONSTARTUP')
297        if startupFile:
298            path = os.path.realpath(os.path.expanduser(startupFile))
299            if os.path.isfile(path):
300                self.ExecStartupFile(path)
301
302        self.write(initialPrompt)
303        self.write(sys.ps1)
304        self.SetInputStart()
305
306    def _DoAutoImports(self):
307        modules = Tf.ScriptModuleLoader().GetModulesDict()
308        for name, mod in modules.items():
309            self.interpreter.runsource('import ' + mod.__name__ +
310                                       ' as ' + name + '\n')
311
312    @_Redirected
313    def ExecStartupFile(self, path):
314        # fix for bug 9104
315        # this sets __file__ in the globals dict while we are execing these
316        # various startup scripts, so that they can access the location from
317        # which they are being run.
318        # also, update the globals dict after we exec the file (bug 9529)
319        self.interpreter.runsource( 'g = dict(globals()); g["__file__"] = ' +
320                                    '"%s"; execfile("%s", g);' % (path, path) +
321                                    'del g["__file__"]; globals().update(g);' )
322        self.SetInputStart()
323        self.lines = []
324
325    def SetInputStart(self):
326        cursor = self.textEdit.textCursor()
327        cursor.movePosition(QtGui.QTextCursor.End)
328        self.textEdit.SetStartOfInput(cursor.position())
329
330    def _QuitSlot(self):
331        if self.readlineEventLoop:
332            if self.readlineEventLoop.isRunning():
333                self.readlineEventLoop.Exit()
334
335    def _TextEditDestroyedSlot(self):
336        self.readlineEventLoop = None
337
338    def _ReturnPressedSlot(self):
339        if self.readlineEventLoop.isRunning():
340            self.readlineEventLoop.Exit()
341        else:
342            self._Run()
343
344    def flush(self):
345        """
346        Simulate stdin, stdout, and stderr.
347        """
348        pass
349
350    def isatty(self):
351        """
352        Simulate stdin, stdout, and stderr.
353        """
354        return 1
355
356    def readline(self):
357        """
358        Simulate stdin, stdout, and stderr.
359        """
360        # XXX: Prevent more than one interpreter from blocking on a readline()
361        #      call.  Starting more than one subevent loop does not work,
362        #      because they must exit in the order that they were created.
363        if Controller._isAnyReadlineEventLoopActive:
364            raise RuntimeError("Simultaneous readline() calls in multiple "
365                               "interpreters are not supported.")
366
367        cursor = self.textEdit.textCursor()
368        cursor.movePosition(QtGui.QTextCursor.End)
369        self.SetInputStart()
370        self.textEdit.setTextCursor(cursor)
371
372        try:
373            Controller._isAnyReadlineEventLoopActive = True
374
375            # XXX TODO - Make this suck less if possible.  We're invoking a
376            # subeventloop here, which means until we return from this
377            # readline we'll never get up to the main event loop.  To avoid
378            # using a subeventloop, we would need to return control to the
379            # main event loop in the main thread, suspending the execution of
380            # the code that called into here, which also lives in the main
381            # thread.  This essentially requires a
382            # co-routine/continuation-based solution capable of dealing with
383            # an arbitrary stack of interleaved Python/C calls (e.g. greenlet).
384            self.readlineEventLoop.Exec()
385        finally:
386            Controller._isAnyReadlineEventLoopActive = False
387
388        cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
389                            QtGui.QTextCursor.MoveAnchor)
390
391        cursor.setPosition(self.textEdit.StartOfInput(),
392                           QtGui.QTextCursor.KeepAnchor)
393        txt = str(cursor.selectedText())
394
395        if len(txt) == 0:
396            return '\n'
397        else:
398            self.write('\n')
399            return txt
400
401    @_Redirected
402    def write(self, text):
403        'Simulate stdin, stdout, and stderr.'
404
405
406        # Move the cursor to the end of the document
407        self.textEdit.moveCursor(QtGui.QTextCursor.End)
408
409        # Clear any existing text format.  We will explicitly set the format
410        # later to something else if need be.
411        self.textEdit.ResetCharFormat()
412
413        # Copy the textEdit's current cursor.
414        cursor = self.textEdit.textCursor()
415        try:
416            # If there's a designated output brush, merge that character format
417            # into the cursor's character format.
418            if self.interpreter.GetOutputBrush():
419                cf = QtGui.QTextCharFormat()
420                cf.setForeground(self.interpreter.GetOutputBrush())
421                cursor.mergeCharFormat(cf)
422
423            # Write the text to the textEdit.
424            cursor.insertText(text)
425
426        finally:
427            # Set the textEdit's cursor to the end of input
428            self.textEdit.moveCursor(QtGui.QTextCursor.End)
429
430    # get the length of a string in pixels bases on our current font
431    @staticmethod
432    def _GetStringLengthInPixels(cf, string):
433        font = cf.font()
434        fm = QtGui.QFontMetrics(font)
435        strlen = fm.width(string)
436        return strlen
437
438    def _CompleteSlot(self):
439
440        cf = self.textEdit.currentCharFormat()
441
442        line = self._GetInputLine()
443        cursor = self.textEdit.textCursor()
444        origPos = cursor.position()
445        cursor.setPosition(self.textEdit.StartOfInput(),
446                           QtGui.QTextCursor.KeepAnchor)
447        text = str(cursor.selectedText())
448        tokens = text.split()
449        token = ''
450        if len(tokens) != 0:
451            token = tokens[-1]
452
453        completions = []
454        p = self.completer.Complete(token,len(completions))
455        while p != None:
456            completions.append(p)
457            p = self.completer.Complete(token, len(completions))
458
459        if len(completions) == 0:
460            return
461
462        elif len(completions) != 1:
463            self.write("\n")
464
465            contentsRect = self.textEdit.contentsRect()
466            # get the width inside the margins to eventually determine the
467            # number of columns in our text table based on the max string width
468            # of our completed words XXX TODO - paging based on widget height
469            width = contentsRect.right() - contentsRect.left()
470
471            maxLength = 0
472            for i in completions:
473                maxLength = max(maxLength, self._GetStringLengthInPixels(cf, i))
474            # pad it a bit
475            maxLength = maxLength + self._GetStringLengthInPixels(cf, '  ')
476
477            # how many columns can we fit on screen?
478            numCols = max(1,width // maxLength)
479            # how many rows do we need to fit our data
480            numRows = (len(completions) // numCols) + 1
481
482            columnWidth = QtGui.QTextLength(QtGui.QTextLength.FixedLength,
483                                            maxLength)
484
485            tableFormat = QtGui.QTextTableFormat()
486            tableFormat.setAlignment(QtCore.Qt.AlignLeft)
487            tableFormat.setCellPadding(0)
488            tableFormat.setCellSpacing(0)
489            tableFormat.setColumnWidthConstraints([columnWidth] * numCols)
490            tableFormat.setBorder(0)
491            cursor = self.textEdit.textCursor()
492
493            # Make the completion table insertion a single edit block
494            cursor.beginEditBlock()
495            cursor.movePosition(QtGui.QTextCursor.End)
496            textTable = cursor.insertTable(numRows, numCols, tableFormat)
497
498            completions.sort()
499            index = 0
500            completionsLength = len(completions)
501
502            for col in range(0,numCols):
503                for row in range(0,numRows):
504                    cellNum = (row * numCols) + col
505                    if (cellNum >= completionsLength):
506                        continue
507                    tableCell = textTable.cellAt(row,col)
508                    cellCursor = tableCell.firstCursorPosition()
509                    cellCursor.insertText(completions[index], cf)
510                    index +=1
511
512            cursor.endEditBlock()
513
514            self.textEdit.setTextCursor(cursor)
515            self.write("\n")
516            if self.more:
517                self.write(sys.ps2)
518            else:
519                self.write(sys.ps1)
520
521            self.SetInputStart()
522            # complete up to the common prefix
523            cp = os.path.commonprefix(completions)
524
525            # make sure that we keep everything after the cursor the same as it
526            # was previously
527            i = line.rfind(token)
528            textToRight = line[i+len(token):]
529            line = line[0:i] + cp + textToRight
530            self.write(line)
531
532            # replace the line and reset the cursor
533            cursor = self.textEdit.textCursor()
534            cursor.setPosition(self.textEdit.StartOfInput() + len(line) -
535                               len(textToRight))
536
537            self.textEdit.setTextCursor(cursor)
538
539        else:
540            i = line.rfind(token)
541            line = line[0:i] + completions[0] + line[i+len(token):]
542
543            # replace the line and reset the cursor
544            cursor = self.textEdit.textCursor()
545
546            cursor.setPosition(self.textEdit.StartOfInput(),
547                               QtGui.QTextCursor.MoveAnchor)
548
549            cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
550                                QtGui.QTextCursor.KeepAnchor)
551
552            cursor.removeSelectedText()
553            cursor.insertText(line)
554            cursor.setPosition(origPos + len(completions[0]) - len(token))
555            self.textEdit.setTextCursor(cursor)
556
557    def _NextSlot(self):
558        if len(self.history):
559            # if we have no history pointer, we can't go forward..
560            if (self.historyPointer == None):
561                return
562            # if we are at the end of our history stack, we can't go forward
563            elif (self.historyPointer == len(self.history) - 1):
564                self._ClearLine()
565                self.write(self.historyInput)
566                self.historyPointer = None
567                return
568            self.historyPointer += 1
569            self._Recall()
570
571    def _PrevSlot(self):
572        if len(self.history):
573            # if we have no history pointer, set it to the most recent
574            # item in the history stack, and stash away our current input
575            if (self.historyPointer == None):
576                self.historyPointer = len(self.history)
577                self.historyInput = self._GetInputLine()
578            # if we are at the end of our history, beep
579            elif (self.historyPointer <= 0):
580                return
581            self.historyPointer -= 1
582            self._Recall()
583
584    def _IsBlank(self, txt):
585        return len(txt.strip()) == 0
586
587    def _GetInputLine(self):
588        cursor = self.textEdit.textCursor()
589        cursor.setPosition(self.textEdit.StartOfInput(),
590                           QtGui.QTextCursor.MoveAnchor)
591        cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
592                            QtGui.QTextCursor.KeepAnchor)
593        txt = str(cursor.selectedText())
594        return txt
595
596    def _ClearLine(self):
597        cursor = self.textEdit.textCursor()
598
599        cursor.setPosition(self.textEdit.StartOfInput(),
600                           QtGui.QTextCursor.MoveAnchor)
601
602        cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
603                            QtGui.QTextCursor.KeepAnchor)
604
605        cursor.removeSelectedText()
606
607    @_Redirected
608    def _Run(self):
609        """
610        Append the last line to the history list, let the interpreter execute
611        the last line(s), and clean up accounting for the interpreter results:
612        (1) the interpreter succeeds
613        (2) the interpreter fails, finds no errors and wants more line(s)
614        (3) the interpreter fails, finds errors and writes them to sys.stderr
615        """
616        self.historyPointer = None
617        inputLine = self._GetInputLine()
618        if (inputLine != ""):
619            self.history.append(inputLine)
620
621        self.lines.append(inputLine)
622        source = '\n'.join(self.lines)
623        self.write('\n')
624        self.more = self.interpreter.runsource(source)
625        if self.more:
626            self.write(sys.ps2)
627            self.SetInputStart()
628        else:
629            self.write(sys.ps1)
630            self.SetInputStart()
631            self.lines = []
632
633    def _Recall(self):
634        """
635        Display the current item from the command history.
636        """
637        self._ClearLine()
638        self.write(self.history[self.historyPointer])
639
640class View(QtWidgets.QTextEdit):
641    """View is a QTextEdit which provides some extra
642    facilities to help implement an interpreter console.  In particular,
643    QTextEdit does not provide for complete control over the buffer being
644    edited.  Some signals are emitted *after* action has already been
645    taken, disallowing controller classes from really controlling the widget.
646    This widget fixes that.
647    """
648
649    returnPressed = QtCore.Signal()
650    requestPrev = QtCore.Signal()
651    requestNext = QtCore.Signal()
652    requestComplete = QtCore.Signal()
653
654    def __init__(self, parent=None):
655        super(View, self).__init__(parent)
656        self.promptLength = 0
657        self.__startOfInput = 0
658        self.setUndoRedoEnabled(False)
659        self.setAcceptRichText(False)
660        self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
661        self.tripleClickTimer = QtCore.QBasicTimer()
662        self.tripleClickPoint = QtCore.QPoint()
663        self._ignoreKeyPresses = True
664        self.ResetCharFormat()
665
666    def SetStartOfInput(self, position):
667        self.__startOfInput = position
668
669    def StartOfInput(self):
670        return self.__startOfInput
671
672    def ResetCharFormat(self):
673        charFormat = QtGui.QTextCharFormat()
674        charFormat.setFontFamily(DefaultFontFamily.MONOSPACE_FONT_FAMILY)
675        self.setCurrentCharFormat(charFormat)
676
677    def _PositionInInputArea(self, position):
678        return position - self.__startOfInput
679
680    def _PositionIsInInputArea(self, position):
681        return self._PositionInInputArea(position) >= 0
682
683    def _CursorIsInInputArea(self):
684        return self._PositionIsInInputArea(self.textCursor().position())
685
686    def _SelectionIsInInputArea(self):
687        if (not self.textCursor().hasSelection()):
688            return False
689        selStart = self.textCursor().selectionStart()
690        selEnd = self.textCursor().selectionEnd()
691        return self._PositionIsInInputArea(selStart) and \
692               self._PositionIsInInputArea(selEnd)
693
694    def _MoveCursorToStartOfInput(self, select=False):
695        cursor = self.textCursor()
696        anchor = QtGui.QTextCursor.MoveAnchor
697
698        if (select):
699            anchor = QtGui.QTextCursor.KeepAnchor
700
701        cursor.movePosition(QtGui.QTextCursor.End, anchor)
702
703        cursor.setPosition(self.__startOfInput, anchor)
704
705        self.setTextCursor(cursor)
706
707    def _MoveCursorToEndOfInput(self, select=False):
708        c = self.textCursor()
709        anchor = QtGui.QTextCursor.MoveAnchor
710        if (select):
711            anchor = QtGui.QTextCursor.KeepAnchor
712
713        c.movePosition(QtGui.QTextCursor.End, anchor)
714        self.setTextCursor(c)
715
716    def _WritableCharsToLeftOfCursor(self):
717        return (self._PositionInInputArea(self.textCursor().position()) > 0)
718
719    def mousePressEvent(self, e):
720        app = QtWidgets.QApplication.instance()
721
722        # is this a triple click?
723        if ((e.button() & QtCore.Qt.LeftButton) and
724             self.tripleClickTimer.isActive() and
725             (e.globalPos() - self.tripleClickPoint).manhattanLength() <
726              app.startDragDistance() ):
727
728            # instead of duplicating the triple click code completely, we just
729            # pass it along. but we modify the selection that comes out of it
730            # to exclude the prompt, if appropriate
731            super(View, self).mousePressEvent(e)
732
733            if (self._CursorIsInInputArea()):
734                selStart = self.textCursor().selectionStart()
735                selEnd = self.textCursor().selectionEnd()
736
737                if (self._PositionInInputArea(selStart) < 0):
738                    # remove selection up until start of input
739                     self._MoveCursorToStartOfInput(False)
740                     cursor = self.textCursor()
741                     cursor.setPosition(selEnd, QtGui.QTextCursor.KeepAnchor)
742                     self.setTextCursor(cursor)
743        else:
744            super(View, self).mousePressEvent(e)
745
746    def mouseDoubleClickEvent(self, e):
747        super(View, self).mouseDoubleClickEvent(e)
748        app = QtWidgets.QApplication.instance()
749        self.tripleClickTimer.start(app.doubleClickInterval(), self)
750        # make a copy here, otherwise tripleClickPoint will always = globalPos
751        self.tripleClickPoint = QtCore.QPoint(e.globalPos())
752
753    def timerEvent(self, e):
754        if (e.timerId() == self.tripleClickTimer.timerId()):
755            self.tripleClickTimer.stop()
756        else:
757            super(View, self).timerEvent(e)
758
759    def enterEvent(self, e):
760        self._ignoreKeyPresses = False
761
762    def leaveEvent(self, e):
763        self._ignoreKeyPresses = True
764
765    def dragEnterEvent(self, e):
766        self._ignoreKeyPresses = False
767        super(View, self).dragEnterEvent(e)
768
769    def dragLeaveEvent(self, e):
770        self._ignoreKeyPresses = True
771        super(View, self).dragLeaveEvent(e)
772
773    def insertFromMimeData(self, source):
774        if not self._CursorIsInInputArea():
775            self._MoveCursorToEndOfInput()
776
777        if source.hasText():
778            text = source.text().replace('\r', '')
779            textLines = text.split('\n')
780            if (textLines[-1] == ''):
781                textLines = textLines[:-1]
782            for i in range(len(textLines)):
783                line = textLines[i]
784                cursor = self.textCursor()
785                cursor.movePosition(QtGui.QTextCursor.End)
786                cursor.insertText(line)
787                cursor.movePosition(QtGui.QTextCursor.End)
788                self.setTextCursor(cursor)
789                if i < len(textLines) - 1:
790                    self.returnPressed.emit()
791
792    def keyPressEvent(self, e):
793        """
794        Handle user input a key at a time.
795        """
796
797        if (self._ignoreKeyPresses):
798            e.ignore()
799            return
800
801        key = e.key()
802
803        ctrl = e.modifiers() & QtCore.Qt.ControlModifier
804        alt = e.modifiers() & QtCore.Qt.AltModifier
805        shift = e.modifiers() & QtCore.Qt.ShiftModifier
806
807        cursorInInput = self._CursorIsInInputArea()
808        selectionInInput = self._SelectionIsInInputArea()
809        hasSelection = self.textCursor().hasSelection()
810        canBackspace = self._WritableCharsToLeftOfCursor()
811        canEraseSelection = selectionInInput and cursorInInput
812        if key == QtCore.Qt.Key_Backspace:
813            if (canBackspace and not hasSelection) or canEraseSelection:
814                super(View, self).keyPressEvent(e)
815        elif key == QtCore.Qt.Key_Delete:
816            if (cursorInInput and not hasSelection) or canEraseSelection:
817                super(View, self).keyPressEvent(e)
818        elif key == QtCore.Qt.Key_Left:
819            pos = self._PositionInInputArea(self.textCursor().position())
820            if pos == 0:
821                e.ignore()
822            else:
823                super(View, self).keyPressEvent(e)
824        elif key == QtCore.Qt.Key_Right:
825            super(View, self).keyPressEvent(e)
826        elif key == QtCore.Qt.Key_Return or key == QtCore.Qt.Key_Enter:
827            # move cursor to end of line.
828            # emit signal to tell controller enter was pressed.
829            if not cursorInInput:
830                self._MoveCursorToStartOfInput(False)
831            cursor = self.textCursor()
832            cursor.movePosition(QtGui.QTextCursor.EndOfBlock)
833            self.setTextCursor(cursor)
834            # emit returnPressed
835            self.returnPressed.emit()
836
837        elif (key == QtCore.Qt.Key_Up
838                or key == QtCore.Qt.Key_Down
839                # support Ctrl+P and Ctrl+N for history
840                # navigation along with arrows
841                or (ctrl and (key == QtCore.Qt.Key_P
842                              or key == QtCore.Qt.Key_N))
843                # support Ctrl+E/End and Ctrl+A/Home for terminal
844                # style nav. to the ends of the line
845                or (ctrl and (key == QtCore.Qt.Key_A
846                              or key == QtCore.Qt.Key_E))
847                or (key == QtCore.Qt.Key_Home
848                    or key == QtCore.Qt.Key_End)):
849            if cursorInInput:
850                if (key == QtCore.Qt.Key_Up or key == QtCore.Qt.Key_P):
851                    self.requestPrev.emit()
852                if (key == QtCore.Qt.Key_Down or key == QtCore.Qt.Key_N):
853                    self.requestNext.emit()
854                if (key == QtCore.Qt.Key_A or key == QtCore.Qt.Key_Home):
855                    self._MoveCursorToStartOfInput(select=shift)
856                if (key == QtCore.Qt.Key_E or key == QtCore.Qt.Key_End):
857                    self._MoveCursorToEndOfInput(select=shift)
858                e.ignore()
859            else:
860                super(View, self).keyPressEvent(e)
861        elif key == QtCore.Qt.Key_Tab:
862            self.AutoComplete()
863            e.accept()
864        elif ((ctrl and key == QtCore.Qt.Key_C) or
865              (shift and key == QtCore.Qt.Key_Insert)):
866            # Copy should never move cursor.
867            super(View, self).keyPressEvent(e)
868        elif ((ctrl and key == QtCore.Qt.Key_X) or
869              (shift and key == QtCore.Qt.Key_Delete)):
870            # Disallow cut from outside the input area so users don't
871            # affect the scrollback buffer.
872            if not selectionInInput:
873                e.ignore()
874            else:
875                super(View, self).keyPressEvent(e)
876        elif (key == QtCore.Qt.Key_Control or
877              key == QtCore.Qt.Key_Alt or
878              key == QtCore.Qt.Key_Shift):
879            # Ignore modifier keypresses by themselves so the cursor
880            # doesn't jump to the end of input when users begin a
881            # key combination.
882            e.ignore()
883        else:
884            # All other keypresses should append to the end of input.
885            if not cursorInInput:
886                self._MoveCursorToEndOfInput()
887            super(View, self).keyPressEvent(e)
888
889    def AutoComplete(self):
890        if self._CursorIsInInputArea():
891            self.requestComplete.emit()
892
893    def _MoveCursorToBeginning(self, select=False):
894        if self._CursorIsInInputArea():
895            self._MoveCursorToStartOfInput(select)
896        else:
897            cursor = self.textCursor()
898            anchor = QtGui.QTextCursor.MoveAnchor
899            if (select):
900                anchor = QtGui.QTextCursor.KeepAnchor
901            cursor.setPosition(0, anchor)
902            self.setTextCursor(cursor)
903
904    def _MoveCursorToEnd(self, select=False):
905        if self._CursorIsInInputArea():
906            self._MoveCursorToEndOfInput(select)
907        else:
908            cursor = self.textCursor()
909            anchor = QtGui.QTextCursor.MoveAnchor
910            if (select):
911                anchor = QtGui.QTextCursor.KeepAnchor
912
913            cursor.setPosition(self.__startOfInput, anchor)
914            cursor.movePosition(QtGui.QTextCursor.Up, anchor)
915            cursor.movePosition(QtGui.QTextCursor.EndOfLine, anchor)
916            self.setTextCursor(cursor)
917
918    def MoveCursorToBeginning(self):
919        self._MoveCursorToBeginning(False)
920
921    def MoveCursorToEnd(self):
922        self._MoveCursorToEnd(False)
923
924    def SelectToTop(self):
925        self._MoveCursorToBeginning(True)
926
927    def SelectToBottom(self):
928        self._MoveCursorToEnd(True)
929
930
931FREQUENTLY_USED = [
932    "dataModel", "stage", "frame", "prim", "property", "spec", "layer"]
933
934
935INITIAL_PROMPT = """
936Use the `usdviewApi` variable to interact with UsdView.
937Type `help(usdviewApi)` to view available API methods and properties.
938
939Frequently used properties:
940{}\n""".format(
941    "".join("    usdviewApi.{} - {}\n".format(
942        name, getattr(UsdviewApi, name).__doc__)
943    for name in FREQUENTLY_USED))
944
945
946class Myconsole(View):
947
948    def __init__(self, parent, usdviewApi):
949        super(Myconsole, self).__init__(parent)
950        self.setObjectName("Myconsole")
951
952        # Inject the UsdviewApi into the interpreter variables.
953        interpreterLocals = vars()
954        interpreterLocals["usdviewApi"] = usdviewApi
955
956        # Make a Controller.
957        self._controller = Controller(self, INITIAL_PROMPT, interpreterLocals)
958
959    def locals(self):
960        return self._controller.interpreter.locals
961