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