soc-2008-mxcurioni: merged changes to revision 14798, compilation works for rendering...
[blender-staging.git] / release / scripts / console.py
1 #!BPY
2
3 """
4 Name: 'Interactive Console'
5 Blender: 237
6 Group: 'System'
7 Tooltip: 'Interactive Python Console'
8 """
9
10 __author__ = "Campbell Barton AKA Ideasman"
11 __url__ = ["Author's homepage, http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun", "Official Python site, http://www.python.org"]
12 __bpydoc__ = """\
13 This is an interactive console, similar to Python's own command line interpreter.  Since it is embedded in Blender, it has access to all Blender Python modules.
14
15 Those completely new to Python are recommended to check the link button above
16 that points to its official homepage, with news, downloads and documentation.
17
18 Usage:<br>
19   Type your code and hit "Enter" to get it executed.<br>
20   - Right mouse click: Console Menu (Save output, etc);<br>
21   - Mousewheel: Scroll text
22   - Arrow keys: command history and cursor;<br>
23   - Shift + Backspace: Backspace whole word;<br>
24   - Shift + Arrow keys: jump words;<br>
25   - Ctrl + (+/- or mousewheel): Zoom text size;<br>
26   - Ctrl + Enter: auto compleate based on variable names and modules loaded -- multiple choices popup a menu;<br>
27   - Shift + Enter: multiline functions -- delays executing code until only Enter is pressed.
28 """
29 __author__ = "Campbell Barton AKA Ideasman"
30 __url__ = ["http://members.iinet.net.au/~cpbarton/ideasman/", "blender", "elysiun"]
31
32 # -------------------------------------------------------------------------- 
33 # ***** BEGIN GPL LICENSE BLOCK ***** 
34
35 # This program is free software; you can redistribute it and/or 
36 # modify it under the terms of the GNU General Public License 
37 # as published by the Free Software Foundation; either version 2 
38 # of the License, or (at your option) any later version. 
39
40 # This program is distributed in the hope that it will be useful, 
41 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
42 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
43 # GNU General Public License for more details. 
44
45 # You should have received a copy of the GNU General Public License 
46 # along with this program; if not, write to the Free Software Foundation, 
47 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
48
49 # ***** END GPL LICENCE BLOCK ***** 
50 # -------------------------------------------------------------------------- 
51
52 import Blender
53 import bpy
54 from Blender import *
55 import sys as python_sys
56 import StringIO
57
58 # Constants
59 __DELIMETERS__ = '. ,=+-*/%<>&~][{}():\t'
60 __VARIABLE_DELIMETERS__ = ' ,=+-*/%<>&~{}():\t'
61
62 __LINE_HISTORY__ = 500
63
64 global __FONT_SIZE__
65
66 __FONT_SIZES__ = ( ('tiny', 10), ('small', 12), ('normalfix', 14), ('large', 16) )
67 __FONT_SIZE__ = 2 # index for the list above, normal default.
68
69 global __CONSOLE_LINE_OFFSET__
70 __CONSOLE_LINE_OFFSET__ = 0
71
72 '''
73 # Generic Blender functions
74 def getActScriptWinRect():
75         area = Window.GetAreaSize()
76         area = (area[0]-1, area[1]-1)
77         for scrInfo in Window.GetScreenInfo(Window.Types['SCRIPT'], 'win', ''):
78                 if scrInfo['vertices'][2]-scrInfo['vertices'][0] == area[0]:
79                         if scrInfo['vertices'][3]-scrInfo['vertices'][1] == area[1]:
80                                 return scrInfo['vertices']
81         return None
82 '''
83
84
85 # menuText, # per group
86 def PupMenuLess(menu, groupSize=35):
87         more = ['   more...']
88         less = ['   less...']
89         
90         menuList= menu.split('|')
91         
92         # No Less Needed, just call.
93         if len(menuList) < groupSize:
94                 return Draw.PupMenu(menu)
95         
96         title = menuList[0].split('%t')[0]
97         
98         # Split the list into groups
99         menuGroups = [[]]
100         for li in menuList[1:]:
101                 if len(menuGroups[-1]) < groupSize:
102                         menuGroups[-1].append(li)
103                 else:
104                         menuGroups.append([li])
105         
106         # Stores teh current menu group we are looking at
107         groupIdx = 0
108         while 1:
109                 # Give us a title with the menu number
110                 numTitle = [ ' '.join([title, str(groupIdx + 1), 'of', str(len(menuGroups)), '%t'])]
111                 if groupIdx == 0:
112                         menuString = '|'.join(numTitle + menuGroups[groupIdx] + more)
113                 elif groupIdx == len(menuGroups)-1:
114                         menuString = '|'.join(numTitle + less + menuGroups[groupIdx])
115                 else: # In the middle somewhere so Show a more and less
116                         menuString = '|'.join(numTitle + less + menuGroups[groupIdx] + more)
117                 result = Draw.PupMenu(menuString)
118                 # User Exit
119                 if result == -1:
120                         return -1
121                 
122                 if groupIdx == 0: # First menu
123                         if result-1 < groupSize:
124                                 return result
125                         else: # must be more
126                                 groupIdx +=1
127                 elif groupIdx == len(menuGroups): # Last Menu
128                         if result == 1: # Must be less
129                                 groupIdx -= 1
130                         else: # Must be a choice
131                                 return result + (groupIdx*groupSize)
132                         
133                 else:   
134                         if result == 1: # Must be less
135                                 groupIdx -= 1
136                         elif result-2 == groupSize:
137                                 groupIdx +=1
138                         else:
139                                 return result - 1 + (groupIdx*groupSize)
140                                 
141
142
143 # Use newstyle classes, Im not bothering with inheretence
144 # but slots are faster.
145 class cmdLine(object):
146         __slots__ = [\
147         'cmd', # is the command string, or any other message
148         'type',# type: 0:user input  1:program feedback  2:error message.  3:option feedback
149         'exe' #  0- not yet executed   1:executed
150         ]
151         def __init__(self, cmd, type, exe):
152                 self.cmd = cmd
153                 self.type = type
154                 self.exe = exe
155
156 # Include external file with internal namespace
157 def include(filename):
158         file = open(filename, 'r')
159         filedata = file.read()
160         file.close()
161         return compile(filedata, filename, 'exec')
162
163 # Writes command line data to a blender text file.
164 def writeCmdData(cmdLineList, type):
165         if type == 3:
166                 typeList = [0,1,2, 3, None] # all
167         else:
168                 typeList = [type] # so we can use athe lists 'in' methiod
169         
170         newText = Text.New('command_output.py', 1)
171         for myCmd in cmdLineList:
172                 if myCmd.type in typeList: # user input
173                         newText.write('%s\n' % myCmd.cmd)
174         Draw.PupMenu('%s written' % newText.name)
175
176 def insertCmdData(cmdBuffer):
177         texts = list(bpy.data.texts)
178         textNames = [tex.name for tex in texts]
179         if textNames:
180                 choice = Draw.PupMenu('|'.join(textNames))
181                 if choice != -1:
182                         text = texts[choice-1]
183                         
184                         # Add the text!
185                         for l in text.asLines():
186                                 cmdBuffer.append(cmdLine('%s ' % l, 0, 0))
187                         Draw.Redraw()
188         
189
190 COLLECTED_VAR_NAMES = {} # a list of keys, each key has a list of absolute paths
191
192 # Pain and simple recursice dir(), accepts a string
193 unused_types = str, dict, list, float, int, str, type, tuple, type(dir), type(None)
194 def rdir(dirString, depth=0):
195         #print ' ' * depth, dirString
196         # MAX DEPTH SET HERE
197         if depth > 5:
198                 # print 'maxdepoth reached.'
199                 return
200                 
201         global COLLECTED_VAR_NAMES
202         dirStringSplit = dirString.split('.')
203         
204         exec('value=' + dirString)      
205         
206         if type(value) in unused_types:
207                 # print 'bad type'
208                 return
209         dirList = dir(value)
210         
211         for dirItem in dirList:
212                 if dirItem.startswith('_'):
213                         continue
214                         
215                 dirData = None
216                 try:
217                         # Rare cases this can mess up, material.shader was a problem.
218                         exec('dirData = %s.%s' % (dirString, dirItem))
219                         #print dirData
220                 except:
221                         # Dont bother with this data.
222                         continue
223                 #print  'HEY', dirData, dirItem
224                 #if type(dirItem) != str:
225                 #       print dirItem, type(dirItem)
226                 
227                 if dirItem not in COLLECTED_VAR_NAMES: # .keys()
228                         COLLECTED_VAR_NAMES[dirItem] = []
229                 
230                 # Add the string
231                 # splitD = dirString.split('"')[-2]
232                 
233                 # Example of dirString
234                 # __CONSOLE_VAR_DICT__["Main"].scenes.active.render
235                 
236                 # Works but can be faster
237                 # splitD = dirString.replace('__CONSOLE_VAR_DICT__["', '').replace('"]', '')
238                 
239                 splitD = dirString[22:].replace('"]', '')
240                 
241                 if splitD not in COLLECTED_VAR_NAMES[dirItem]:
242                         # print dirItem, dirString, splitD,
243                         COLLECTED_VAR_NAMES[dirItem].append(splitD)
244                 
245                 
246                 # Stops recursice stuff, overlooping
247                 #print type(dirItem)
248                 #if type(dirData) == types.ClassType or \
249                 #        type(dirData) == types.ModuleType:
250                 type_dirData = type(dirData)
251                 if type_dirData not in unused_types:
252                         # print type(dirData), dirItem
253                         # Dont loop up dirs for strings ints etc.
254                         if dirItem not in dirStringSplit:
255                                 rdir( '%s.%s' % (dirString, dirItem), depth+1)
256                 '''
257                 elif depth == 0: # Add local variables
258                         # print type(dirData), dirItem
259                         # Dont loop up dirs for strings ints etc.
260                         if dirItem not in dirStringSplit:
261                                 rdir( '%s.%s' % (dirString, dirItem), depth+1)
262                 '''
263
264 def recursive_dir():
265         global COLLECTED_VAR_NAMES
266         
267         for name in __CONSOLE_VAR_DICT__: # .keys()
268                 if not name.startswith('_'): # Dont pick names like __name__
269                         rdir('__CONSOLE_VAR_DICT__["%s"]' % name)
270                         #print COLLECTED_VAR_NAMES
271                         COLLECTED_VAR_NAMES[name] = [''] 
272         return COLLECTED_VAR_NAMES
273
274 # Runs the code line(s) the user has entered and handle errors
275 # As well as feeding back the output into the blender window.
276 def runUserCode(__USER_CODE_STRING__):
277         global __CONSOLE_VAR_DICT__ # We manipulate the variables here. loading and saving from localspace to this global var.
278         
279         # Open A File like object to write all output to, that would useually be printed. 
280         python_sys.stdout.flush() # Get rid of whatever came before
281         __FILE_LIKE_STRING__ = StringIO.StringIO() # make a new file like string, this saves up from making a file.
282         __STD_OUTPUT__ = python_sys.stdout # we need to store the normal output.
283         python_sys.stdout=__FILE_LIKE_STRING__ # Now anything printed will be written to the file like string.
284         
285         # Try and run the user entered line(s)
286         try:
287                 # Load all variabls from global dict to local space.
288                 __TMP_VAR_NAME__ = __TMP_VAR__ = '' # so as not to raise an error when del'ing
289
290                 for __TMP_VAR_NAME__, __TMP_VAR__ in __CONSOLE_VAR_DICT__.items():
291                         exec('%s%s' % (__TMP_VAR_NAME__,'=__TMP_VAR__'))
292                 del __TMP_VAR_NAME__
293                 del __TMP_VAR__
294                 
295                 # Now all the vars are loaded, execute the code. # Newline thanks to phillip,
296                 exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'single')) #exec(compile(__USER_CODE_STRING__, 'blender_cmd.py', 'exec'))
297                 
298                 # Flush global dict, allow the user to remove items.
299                 __CONSOLE_VAR_DICT__ = {}
300
301                 __TMP_VAR_NAME__ = '' # so as not to raise an error when del'ing        
302                 # Write local veriables to global __CONSOLE_VAR_DICT__
303                 for __TMP_VAR_NAME__ in dir():
304                         if      __TMP_VAR_NAME__ != '__FILE_LIKE_STRING__' and\
305                                         __TMP_VAR_NAME__ != '__STD_OUTPUT__' and\
306                                         __TMP_VAR_NAME__ != '__TMP_VAR_NAME__' and\
307                                         __TMP_VAR_NAME__ != '__USER_CODE_STRING__':
308                                 
309                                 # Execute the local > global coversion.
310                                 exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__))
311                 del __TMP_VAR_NAME__
312         
313         except: # Prints the REAL exception.
314                 error = '%s:  %s' % (python_sys.exc_type, python_sys.exc_value)         
315                 for errorLine in error.split('\n'):
316                         cmdBuffer.append(cmdLine(errorLine, 2, None)) # new line to type into
317         
318         python_sys.stdout = __STD_OUTPUT__ # Go back to output to the normal blender console
319         
320         # Copy all new output to cmdBuffer
321         
322         __FILE_LIKE_STRING__.seek(0) # the readline function requires that we go back to the start of the file.
323         
324         for line in __FILE_LIKE_STRING__.readlines():
325                 cmdBuffer.append(cmdLine(line, 1, None))
326                 
327         cmdBuffer.append(cmdLine(' ', 0, 0)) # new line to type into
328         python_sys.stdout=__STD_OUTPUT__
329         __FILE_LIKE_STRING__.close()
330
331
332
333
334
335 #------------------------------------------------------------------------------#
336 #                             event handling code                              #
337 #------------------------------------------------------------------------------#
338 def handle_event(evt, val):
339         
340         # Insert Char into the cammand line
341         def insCh(ch): # Instert a char
342                 global cmdBuffer
343                 global cursor
344                 # Later account for a cursor variable
345                 cmdBuffer[-1].cmd = ('%s%s%s' % ( cmdBuffer[-1].cmd[:cursor], ch, cmdBuffer[-1].cmd[cursor:]))
346         
347         #------------------------------------------------------------------------------#
348         #                        Define Complex Key Actions                            #
349         #------------------------------------------------------------------------------#
350         def actionEnterKey():
351                 global histIndex, cursor, cmdBuffer
352                 
353                 def getIndent(string):
354                         # Gather white space to add in the previous line
355                         # Ignore the last char since its padding.
356                         whiteSpace = ''
357                         #for i in range(len(cmdBuffer[-1].cmd)):
358                         for i in xrange(len(string)-1):
359                                 if cmdBuffer[-1].cmd[i] == ' ' or cmdBuffer[-1].cmd[i] == '\t':
360                                         whiteSpace += string[i]
361                                 else:
362                                         break
363                         return whiteSpace
364                 
365                 # Autocomplete
366                 if Window.GetKeyQualifiers() & Window.Qual.CTRL:
367                         actionAutoCompleate()
368                         return
369                 
370                 # Are we in the middle of a multiline part or not?
371                 # try be smart about it
372                 if cmdBuffer[-1].cmd.split('#')[0].rstrip().endswith(':'):
373                         # : indicates an indent is needed
374                         cmdBuffer.append(cmdLine('\t%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
375                         print ': indicates an indent is needed'         
376                 
377                 elif cmdBuffer[-1].cmd[0] in [' ', '\t'] and len(cmdBuffer[-1].cmd) > 1 and cmdBuffer[-1].cmd.split():
378                         # white space at the start means he havnt finished the multiline.
379                         cmdBuffer.append(cmdLine('%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
380                         print 'white space at the start means he havnt finished the multiline.'
381                 
382                 elif Window.GetKeyQualifiers() & Window.Qual.SHIFT:
383                         # Crtl forces multiline
384                         cmdBuffer.append(cmdLine('%s ' % getIndent(cmdBuffer[-1].cmd), 0, 0))
385                         print 'Crtl forces multiline'                   
386                 
387                 else: # Execute multiline code block
388                         
389                         # Multiline code will still run with 1 line,
390                         multiLineCode = ['if 1:'] # End of the multiline first.
391                         
392                         # Seek the start of the file multiline
393                         i = 1
394                         while cmdBuffer[-i].exe == 0:
395                                 i+=1
396                         
397                         while i > 1:
398                                 i-=1
399                                 
400                                 if cmdBuffer[-i].cmd == ' ':# Tag as an output type so its not used in the key history
401                                         cmdBuffer[-i].type = 1
402                                 else: # Tab added at the start for added if 1: statement
403                                         multiLineCode.append('\t%s' % cmdBuffer[-i].cmd )
404                                 
405                                 # Mark as executed
406                                 cmdBuffer[-i].exe = 1                           
407                                 
408                         multiLineCode.append('\tpass') # reverse will make this the start.                      
409                         
410                         # Dubug, print the code that is executed.
411                         #for m in multiLineCode: print m
412                         
413                         runUserCode('\n'.join(multiLineCode))
414                         
415                         # Clear the output based on __LINE_HISTORY__
416                         if len(cmdBuffer) > __LINE_HISTORY__:
417                                 cmdBuffer = cmdBuffer[-__LINE_HISTORY__:]
418                 
419                 histIndex = cursor = -1 # Reset cursor and history
420         
421         def actionUpKey():
422                 global histIndex, cmdBuffer
423                 if abs(histIndex)+1 >= len(cmdBuffer):
424                         histIndex = -1
425                 histIndex_orig = histIndex
426                 histIndex -= 1
427                 
428                 while   (cmdBuffer[histIndex].type != 0 and abs(histIndex) < len(cmdBuffer)) or \
429                                 ( cmdBuffer[histIndex].cmd == cmdBuffer[histIndex_orig].cmd):
430                         histIndex -= 1
431                         
432                 if cmdBuffer[histIndex].type == 0: # we found one
433                         cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd                    
434         
435         def actionDownKey():
436                 global histIndex, cmdBuffer
437                 if histIndex >= -2:
438                         histIndex = -len(cmdBuffer)
439                 histIndex_orig = histIndex
440                 histIndex += 1
441                 while   (cmdBuffer[histIndex].type != 0 and histIndex != -2) or \
442                                 ( cmdBuffer[histIndex].cmd == cmdBuffer[histIndex_orig].cmd):
443                         
444                         histIndex += 1
445                         
446                 if cmdBuffer[histIndex].type == 0: # we found one
447                         cmdBuffer[-1].cmd = cmdBuffer[histIndex].cmd
448         
449         def actionRightMouse():
450                 global __FONT_SIZE__
451                 choice = Draw.PupMenu('Console Menu%t|Write Input Data (white)|Write Output Data (blue)|Write Error Data (red)|Write All Text|%l|Insert Blender text|%l|Font Size|%l|Quit')
452                 
453                 if choice == 1:
454                         writeCmdData(cmdBuffer, 0) # type 0 user
455                 elif choice == 2:
456                         writeCmdData(cmdBuffer, 1) # type 1 user output
457                 elif choice == 3:
458                         writeCmdData(cmdBuffer, 2) # type 2 errors
459                 elif choice == 4:
460                         writeCmdData(cmdBuffer, 3) # All
461                 elif choice == 6:
462                         insertCmdData(cmdBuffer) # Insert text from Blender and run it.
463                 elif choice == 8:
464                         # Fontsize.
465                         font_choice = Draw.PupMenu('Font Size%t|Large|Normal|Small|Tiny')
466                         if font_choice != -1:
467                                 if font_choice == 1:
468                                         __FONT_SIZE__ = 3
469                                 elif font_choice == 2:
470                                         __FONT_SIZE__ = 2
471                                 elif font_choice == 3:
472                                         __FONT_SIZE__ = 1
473                                 elif font_choice == 4:
474                                         __FONT_SIZE__ = 0
475                                 Draw.Redraw()
476                                 
477                 elif choice == 10: # Exit
478                         Draw.Exit()
479         
480         
481         # Auto compleating, quite complex- use recutsice dir for the moment.
482         def actionAutoCompleate(): # Ctrl + Tab
483                 if not cmdBuffer[-1].cmd[:cursor].split():
484                         return
485                 
486                 
487                 RECURSIVE_DIR = recursive_dir()
488                 
489                 # get last name of user input
490                 editVar = cmdBuffer[-1].cmd[:cursor]
491                 # Split off spaces operators etc from the staryt of the command so we can use the startswith function.
492                 for splitChar in __VARIABLE_DELIMETERS__:
493                         editVar = editVar[:-1].split(splitChar)[-1] + editVar[-1]
494                 
495                 
496                 # Now we should have the var by its self
497                 if editVar:
498                         possibilities = []
499                         
500                         for __TMP_VAR_NAME__ in RECURSIVE_DIR: #.keys():
501                                 #print '\t', __TMP_VAR_NAME__
502                                 if __TMP_VAR_NAME__ == editVar:
503                                         # print 'ADITVAR IS A VAR'
504                                         pass
505                                 '''
506                                 elif __TMP_VAR_NAME__.startswith( editVar ):
507                                         print __TMP_VAR_NAME__, 'aaa'
508                                         possibilities.append( __TMP_VAR_NAME__ )
509                                 '''
510                                 possibilities.append( __TMP_VAR_NAME__ )
511                         
512                         if len(possibilities) == 1:
513                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], possibilities[0], cmdBuffer[-1].cmd[cursor:]))    
514                         
515                         elif possibilities: # If its not just []
516                                 # -1 with insert is the second last.
517                                 
518                                 # Text choice
519                                 #cmdBuffer.insert(-1, cmdLine('options: %s' % ' '.join(possibilities), 3, None))
520                                 
521                                 menuList = [] # A lits of tuples- ABSOLUTE, RELATIVE
522                                 
523                                 for __TMP_VAR_NAME__ in possibilities:
524                                         for usage in RECURSIVE_DIR[__TMP_VAR_NAME__]:
525                                                 # Account for non absolute (variables for eg.)
526                                                 if usage: # not ''
527                                                         absName = '%s.%s' % (usage, __TMP_VAR_NAME__)
528                                                         
529                                                         if __TMP_VAR_NAME__.startswith(editVar):
530                                                                 menuList.append( # Used for names and can be entered when pressing shift.
531                                                                   (absName, # Absolute name
532                                                                   __TMP_VAR_NAME__) # Relative name, non shift
533                                                                   )
534                                                 
535                                                 #else:
536                                                 #       if absName.find(editVar) != -1:
537                                                 #               menuList.append((__TMP_VAR_NAME__, __TMP_VAR_NAME__)) # Used for names and can be entered when pressing shift.
538                                 
539                                 # No items to display? no menu
540                                 if not menuList:
541                                         return
542                                         
543                                 menuList.sort()
544                                 
545                                 choice = PupMenuLess( # Menu for the user to choose the autocompleate
546                                 'Choices (Shift for local name, Ctrl for Docs)%t|' + # Title Text
547                                 '|'.join(['%s,  %s' % m for m in menuList])) # Use Absolute names m[0]
548                                 
549                                 if choice != -1:
550                                         if Window.GetKeyQualifiers() & Window.Qual.CTRL:  # Help
551                                                 cmdBuffer[-1].cmd = ('help(%s%s) ' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][0]))    
552                                         elif Window.GetKeyQualifiers() & Window.Qual.SHIFT:  # Put in the long name
553                                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][1], cmdBuffer[-1].cmd[cursor:]))    
554                                         else: # Only paste in the Short name
555                                                 cmdBuffer[-1].cmd = ('%s%s%s' % (cmdBuffer[-1].cmd[:cursor - len(editVar)], menuList[choice-1][0], cmdBuffer[-1].cmd[cursor:]))    
556                                                 
557                                                 
558                 else:
559                         # print 'NO EDITVAR'
560                         return
561                 
562         # ------------------end------------------ #
563         
564         # Quit from menu only
565         #if (evt == Draw.ESCKEY and not val):
566         #       Draw.Exit()
567         if evt == Draw.MOUSEX or evt == Draw.MOUSEY: # AVOID TOO MANY REDRAWS.
568                 return  
569         
570         
571         global cursor
572         global histIndex        
573         global __FONT_SIZE__
574         global __CONSOLE_LINE_OFFSET__
575         
576         ascii = Blender.event
577         
578         resetScroll = True
579         
580         #------------------------------------------------------------------------------#
581         #                             key codes and key handling                       #
582         #------------------------------------------------------------------------------#
583         
584         # UP DOWN ARROW KEYS, TO TRAVERSE HISTORY
585         if (evt == Draw.UPARROWKEY and val): actionUpKey()
586         elif (evt == Draw.DOWNARROWKEY and val): actionDownKey()
587         
588         elif (evt == Draw.RIGHTARROWKEY and val):
589                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
590                         wordJump = False
591                         newCursor = cursor+1
592                         while newCursor<0:
593                                 
594                                 if cmdBuffer[-1].cmd[newCursor] not in __DELIMETERS__:
595                                         newCursor+=1
596                                 else:
597                                         wordJump = True
598                                         break
599                         if wordJump: # Did we find a new cursor pos?
600                                 cursor = newCursor
601                         else:
602                                 cursor = -1 # end of line
603                 else:
604                         cursor +=1
605                         if cursor > -1:
606                                 cursor = -1
607         
608         elif (evt == Draw.LEFTARROWKEY and val):
609                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
610                         wordJump = False
611                         newCursor = cursor-1
612                         while abs(newCursor) < len(cmdBuffer[-1].cmd):
613                                 
614                                 if cmdBuffer[-1].cmd[newCursor] not in __DELIMETERS__ or\
615                                 newCursor == cursor:
616                                         newCursor-=1
617                                 else:
618                                         wordJump = True
619                                         break
620                         if wordJump: # Did we find a new cursor pos?
621                                 cursor = newCursor
622                         else: 
623                                 cursor = -len(cmdBuffer[-1].cmd) # Start of line
624                         
625                 else:
626                         if len(cmdBuffer[-1].cmd) > abs(cursor):
627                                 cursor -=1
628         
629         elif (evt == Draw.HOMEKEY and val):
630                 cursor  = -len(cmdBuffer[-1].cmd)
631         
632         elif (evt == Draw.ENDKEY and val):
633                 cursor = -1
634         
635         elif (evt == Draw.TABKEY and val):
636                 insCh('\t')     
637         
638         elif (evt == Draw.BACKSPACEKEY and val):
639                 if Window.GetKeyQualifiers() & Window.Qual.SHIFT:
640                         i = -1
641                         for d in __DELIMETERS__:
642                                 i = max(i, cmdBuffer[-1].cmd[:cursor-1].rfind(d))
643                         if i == -1:
644                                 i=0
645                         cmdBuffer[-1].cmd = ('%s%s' % (cmdBuffer[-1].cmd[:i] , cmdBuffer[-1].cmd[cursor:]))
646                         
647                 else:
648                         # Normal backspace.
649                         cmdBuffer[-1].cmd = ('%s%s' % (cmdBuffer[-1].cmd[:cursor-1] , cmdBuffer[-1].cmd[cursor:]))
650                         
651         elif (evt == Draw.DELKEY and val) and cursor < -1:
652                 cmdBuffer[-1].cmd = cmdBuffer[-1].cmd[:cursor] + cmdBuffer[-1].cmd[cursor+1:]
653                 cursor +=1
654         
655         elif ((evt == Draw.RETKEY or evt == Draw.PADENTER) and val):
656                 actionEnterKey()
657                         
658         elif (evt == Draw.RIGHTMOUSE and not val): actionRightMouse(); return
659         
660         elif (evt == Draw.PADPLUSKEY or evt == Draw.EQUALKEY or evt == Draw.WHEELUPMOUSE) and val and Window.GetKeyQualifiers() & Window.Qual.CTRL:
661                 __FONT_SIZE__ += 1
662                 __FONT_SIZE__ = min(len(__FONT_SIZES__)-1, __FONT_SIZE__)
663         elif (evt == Draw.PADMINUS or evt == Draw.MINUSKEY or evt == Draw.WHEELDOWNMOUSE) and val and Window.GetKeyQualifiers() & Window.Qual.CTRL:
664                 __FONT_SIZE__ -=1
665                 __FONT_SIZE__ = max(0, __FONT_SIZE__)
666         
667         
668         elif evt == Draw.WHEELUPMOUSE and val:
669                 __CONSOLE_LINE_OFFSET__ += 1
670                 __CONSOLE_LINE_OFFSET__ = min(len(cmdBuffer)-2, __CONSOLE_LINE_OFFSET__)
671                 resetScroll = False
672                 
673         elif evt == Draw.WHEELDOWNMOUSE and val:
674                 __CONSOLE_LINE_OFFSET__ -= 1
675                 __CONSOLE_LINE_OFFSET__ = max(0, __CONSOLE_LINE_OFFSET__)
676                 resetScroll = False
677         
678
679         elif ascii:
680                 insCh(chr(ascii))
681         else:
682                 return # dont redraw.
683         
684         # If the user types in anything then scroll to bottom.
685         if resetScroll:
686                 __CONSOLE_LINE_OFFSET__ = 0
687         Draw.Redraw()
688
689
690 def draw_gui():
691         # Get the bounds from ObleGL directly
692         __CONSOLE_RECT__ = BGL.Buffer(BGL.GL_FLOAT, 4)
693         BGL.glGetFloatv(BGL.GL_SCISSOR_BOX, __CONSOLE_RECT__) 
694         __CONSOLE_RECT__= __CONSOLE_RECT__.list
695         
696         # Clear the screen
697         BGL.glClearColor(0.0, 0.0, 0.0, 1.0)
698         BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)         # use it to clear the color buffer
699         
700         
701         # Fixed margin. use a margin since 0 margin can be hard to seewhen close to a crt's edge.
702         margin = 4
703         
704         # Draw cursor location colour
705         if __CONSOLE_LINE_OFFSET__ == 0:
706                 cmd2curWidth = Draw.GetStringWidth(cmdBuffer[-1].cmd[:cursor], __FONT_SIZES__[__FONT_SIZE__][0])
707                 BGL.glColor3f(0.8, 0.2, 0.2)
708                 if cmd2curWidth == 0:
709                         BGL.glRecti(margin,2,margin+2, __FONT_SIZES__[__FONT_SIZE__][1]+2)
710                 else:
711                         BGL.glRecti(margin + cmd2curWidth-2,2, margin+cmd2curWidth, __FONT_SIZES__[__FONT_SIZE__][1]+2)
712         
713         BGL.glColor3f(1,1,1)
714         # Draw the set of cammands to the buffer
715         consoleLineIdx = __CONSOLE_LINE_OFFSET__ + 1
716         wrapLineIndex = 0
717         while consoleLineIdx < len(cmdBuffer) and  __CONSOLE_RECT__[3] > (consoleLineIdx - __CONSOLE_LINE_OFFSET__) * __FONT_SIZES__[__FONT_SIZE__][1]:
718                 if cmdBuffer[-consoleLineIdx].type == 0:
719                         BGL.glColor3f(1, 1, 1)
720                 elif cmdBuffer[-consoleLineIdx].type == 1:
721                         BGL.glColor3f(.3, .3, 1)
722                 elif cmdBuffer[-consoleLineIdx].type == 2:
723                         BGL.glColor3f(1.0, 0, 0)
724                 elif cmdBuffer[-consoleLineIdx].type == 3:
725                         BGL.glColor3f(0, 0.8, 0)
726                 else:  
727                         BGL.glColor3f(1, 1, 0)
728                 
729                 if consoleLineIdx == 1: # user input
730                         BGL.glRasterPos2i(margin, (__FONT_SIZES__[__FONT_SIZE__][1] * (consoleLineIdx-__CONSOLE_LINE_OFFSET__)) - 8)
731                         Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZES__[__FONT_SIZE__][0])             
732                 else:
733                         BGL.glRasterPos2i(margin, (__FONT_SIZES__[__FONT_SIZE__][1] * ((consoleLineIdx-__CONSOLE_LINE_OFFSET__)+wrapLineIndex)) - 8)
734                         Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZES__[__FONT_SIZE__][0])
735
736                 # Wrapping is totally slow, can even hang blender - dont do it!
737                 '''
738                 if consoleLineIdx == 1: # NEVER WRAP THE USER INPUT
739                         BGL.glRasterPos2i(margin, (__FONT_SIZES__[__FONT_SIZE__][1] * (consoleLineIdx-__CONSOLE_LINE_OFFSET__)) - 8)
740                         # BUG, LARGE TEXT DOSENT DISPLAY
741                         Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZES__[__FONT_SIZE__][0])
742                         
743                 
744                 else: # WRAP?
745                         # LINE WRAP
746                         if Draw.GetStringWidth(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZES__[__FONT_SIZE__][0]) >  __CONSOLE_RECT__[2]:
747                                 wrapLineList = []
748                                 copyCmd = [cmdBuffer[-consoleLineIdx].cmd, '']
749                                 while copyCmd != ['','']:
750                                         while margin + Draw.GetStringWidth(copyCmd[0], __FONT_SIZES__[__FONT_SIZE__][0]) > __CONSOLE_RECT__[2]:
751                                                 #print copyCmd
752                                                 copyCmd[1] = '%s%s'% (copyCmd[0][-1], copyCmd[1]) # Add the char on the end
753                                                 copyCmd[0] = copyCmd[0][:-1]# remove last chat
754                                         
755                                         # Now we have copyCmd[0] at a good length we can print it.                                      
756                                         if copyCmd[0] != '':
757                                                 wrapLineList.append(copyCmd[0])
758                                         
759                                         copyCmd[0]=''
760                                         copyCmd = [copyCmd[1], copyCmd[0]]
761                                 
762                                 # Now we have a list of lines, draw them (OpenGLs reverse ordering requires this odd change)
763                                 wrapLineList.reverse()
764                                 for wline in wrapLineList:
765                                         BGL.glRasterPos2i(margin, (__FONT_SIZES__[__FONT_SIZE__][1]*((consoleLineIdx-__CONSOLE_LINE_OFFSET__) + wrapLineIndex)) - 8)
766                                         Draw.Text(wline, __FONT_SIZES__[__FONT_SIZE__][0])
767                                         wrapLineIndex += 1
768                                 wrapLineIndex-=1 # otherwise we get a silly extra line.
769                                 
770                         else: # no wrapping.
771                                 
772                                 BGL.glRasterPos2i(margin, (__FONT_SIZES__[__FONT_SIZE__][1] * ((consoleLineIdx-__CONSOLE_LINE_OFFSET__)+wrapLineIndex)) - 8)
773                                 Draw.Text(cmdBuffer[-consoleLineIdx].cmd, __FONT_SIZES__[__FONT_SIZE__][0])
774                 '''
775                 consoleLineIdx += 1
776                         
777
778 # This recieves the event index, call a function from here depending on the event.
779 def handle_button_event(evt):
780         pass
781
782
783 # Run the console
784 __CONSOLE_VAR_DICT__ = {} # Initialize var dict
785
786
787 # Print Startup lines, add __bpydoc__ to the console startup.
788 cmdBuffer = []
789 for l in __bpydoc__.split('<br>'):
790         cmdBuffer.append( cmdLine(l, 1, None) )
791         
792
793 histIndex = cursor = -1 # How far back from the first letter are we? - in current CMD line, history if for moving up and down lines.
794
795 # Autoexec, startup code.
796 scriptDir = Get('scriptsdir')
797 console_autoexec = None
798 if scriptDir:
799         if not scriptDir.endswith(Blender.sys.sep):
800                 scriptDir += Blender.sys.sep
801         
802         console_autoexec  = '%s%s' % (scriptDir, 'console_autoexec.py')
803
804         if not sys.exists(console_autoexec):
805                 # touch the file
806                 try:
807                         open(console_autoexec, 'w').close()
808                         cmdBuffer.append(cmdLine('...console_autoexec.py not found, making new in scripts dir', 1, None))
809                 except:
810                         cmdBuffer.append(cmdLine('...console_autoexec.py could not write, this is ok', 1, None))
811                         scriptDir = None # make sure we only use this for console_autoexec.py
812         
813         if not sys.exists(console_autoexec):
814                 console_autoexec = None
815         
816         else:
817                 cmdBuffer.append(cmdLine('...Using existing console_autoexec.py in scripts dir', 1, None))
818
819
820
821 #-Autoexec---------------------------------------------------------------------#
822 # Just use the function to jump into local naming mode.
823 # This is so we can loop through all of the autoexec functions / vars and add them to the __CONSOLE_VAR_DICT__
824 def include_console(includeFile):
825         global __CONSOLE_VAR_DICT__ # write autoexec vars to this.
826         
827         # Execute an external py file as if local
828         exec(include(includeFile))
829
830 def standard_imports():
831         # Write local to global __CONSOLE_VAR_DICT__ for reuse,
832         for ls in (dir(), dir(Blender)):
833                 for __TMP_VAR_NAME__ in ls:
834                         # Execute the local > global coversion.
835                         exec('%s%s' % ('__CONSOLE_VAR_DICT__[__TMP_VAR_NAME__]=', __TMP_VAR_NAME__))
836         
837         exec('%s%s' % ('__CONSOLE_VAR_DICT__["bpy"]=', 'bpy'))
838
839 if scriptDir and console_autoexec:
840         include_console(console_autoexec) # pass the blender module
841
842 standard_imports() # import Blender and bpy
843
844 #-end autoexec-----------------------------------------------------------------#
845
846
847 # Append new line to write to
848 cmdBuffer.append(cmdLine(' ', 0, 0))
849
850 #------------------------------------------------------------------------------#
851 #                    register the event handling code, GUI                     #
852 #------------------------------------------------------------------------------#
853 def main():
854         Draw.Register(draw_gui, handle_event, handle_button_event)
855
856 if __name__ == '__main__':
857         main()