소스 검색

Squashed 'src/ted2go/' changes from 4bb0ed65..fd26c341

fd26c341 Added 'quiet' option to wget/curl in modulemanager.
f5fdea87 Don't show item "Bananas showcase" if there is no showcase data inside bananas folder.
40e26d0a Merge branch 'update_moduled' into dev
89ebdf96 Done with "Update / Rebuild modules" dialog.
7652b334 wip update modules.
42357ad6 Merge branch 'master' into dev
21763b64 Merge branch 'master' into update_moduled
eec1a511 WIP - new "Update modules" dialog.
d5417267 Project view -- modules - added items "update modules" (debug / release / debug+release).
d49ef0b3 Added item "Help -- Bananas showcase".
343be735 Parser - fixed call to nullable scope.
86bfdfe7 Open bananas showcase when first start app.
4ff8ce76 Done bananas viewer.
8ac6af78 WIP bananas document.
5813a286 Preferences dialog - group options by tabs. Fixed switching to text overwrite mode by Insert key.
f548dacc Added option "Auto indentation" that allow us to disable auto indent.
fff37a8e Improved monkey2's root folder detection. Should work from usb-drive now.
53aec319 Some fixes in IRC chat.
79934f58 Merge branch 'dev' of https://github.com/engor/Ted2Go into irc
6af674a6 Merge pull request #31 from Hezkore/dev
80f738b4 Merge branch 'dev' into dev
f73ab8a0 Possibly, fixed crash when close app just right after run it. Prefs - added 'reset font' button.
d87d6473 Fixed incorrect cursor-row selection in wordwrap mode.
29386c9e CodeMapView! * now it extends View not CodeTextView. and render original codetextview; * added option in preferences to show/hide codemap; * fixed dragging when content is less than visible rect. Issues: * text doesn't draw on selected area; * bottom map's part can be over-rendered and looks brighter (wait for mojox improv. for my purposes).
0142b747 Fixed wordwrap mode (dirty hack with multi-rendering original codetextview with different scroll positions).
83ee3108 Fixed output console auto-scrolling. Improved CodeMap - but wordwrap mode doesn't work.
a1c52eb5 IRC View/Tab added
ce4fc754 Renamed to uppercase ABOUT.HTML.
340c3288 Merge pull request #30 from Hezkore/dev
e55d6955 Added WIP code map
0ed29983 Merge pull request #29 from Hezkore/dev
32993136 New option for disabling project view file icons
8ef4a3e1 Theme updates
4ca110f1 wip
450c13f4 Starting to add bananas showcase.

git-subtree-dir: src/ted2go
git-subtree-split: fd26c3410812833b3637eed7a503d88f3265c5d4
Mark Sibly 8 년 전
부모
커밋
2f1973fb85
47개의 변경된 파일3764개의 추가작업 그리고 414개의 파일을 삭제
  1. 102 31
      MainWindow.monkey2
  2. 52 22
      Prefs.monkey2
  3. 11 2
      ProcessReader.monkey2
  4. 73 21
      Ted2.monkey2
  5. 45 58
      action/BuildActions.monkey2
  6. 8 1
      action/EditActions.monkey2
  7. 1 1
      action/FileActions.monkey2
  8. 7 0
      action/HelpActions.monkey2
  9. BIN
      assets/fonts/MesloLGSDZ-Bold.ttf
  10. 3 1
      assets/themes/default-tmp.json
  11. BIN
      assets/themes/irc/notice.png
  12. BIN
      assets/themes/prime_assets/button_skin.png
  13. BIN
      assets/themes/prime_assets/checkbox_icons.png
  14. BIN
      assets/themes/prime_assets/dialog_skin.png
  15. BIN
      assets/themes/prime_assets/progressbar_icons.png
  16. BIN
      assets/themes/prime_assets/square.png
  17. BIN
      assets/themes/prime_assets/tabbutton_skin.png
  18. BIN
      assets/themes/prime_assets/tabclose_icons.png
  19. BIN
      assets/themes/prime_assets/treeview_icons.png
  20. 50 8
      assets/themes/ted2-default.json
  21. 455 0
      assets/themes/theme-prime-base.json
  22. 9 0
      assets/themes/theme-prime-blue.json
  23. 9 0
      assets/themes/theme-prime-red.json
  24. 1 1
      assets/themes/theme-smooth.json
  25. 1 1
      assets/themes/theme-warm.json
  26. 3 1
      assets/themes/themes.json
  27. 31 0
      dialog/DialogExt.monkey2
  28. 231 131
      dialog/PrefsDialog.monkey2
  29. 187 0
      dialog/UpdateModulesDialog.monkey2
  30. 426 0
      document/BananasDocument.monkey2
  31. 60 27
      document/CodeDocument.monkey2
  32. 4 4
      document/Ted2Document.monkey2
  33. 1 8
      eventfilter/Monkey2KeyEventFilter.monkey2
  34. 4 4
      product/ModuleManager.monkey2
  35. 1 1
      syntax/Keywords.monkey2
  36. 0 38
      utils/JsonUtils.monkey2
  37. 23 0
      utils/Utils.monkey2
  38. 44 0
      utils/jsonutils.monkey2
  39. 214 0
      view/CodeMapView.monkey2
  40. 38 41
      view/CodeTextView.monkey2
  41. 6 4
      view/ConsoleViewExt.monkey2
  42. 243 0
      view/FileBrowserExt.monkey2
  43. 1342 0
      view/IRCView.monkey2
  44. 23 2
      view/ProjectBrowserView.monkey2
  45. 30 5
      view/ProjectView.monkey2
  46. 25 0
      view/ScrollableViewExt.monkey2
  47. 1 1
      view/StatusBarView.monkey2

+ 102 - 31
MainWindow.monkey2

@@ -15,7 +15,10 @@ Namespace ted2go
 Global MainWindow:MainWindowInstance
 
 Class MainWindowInstance Extends Window
-
+	
+	Field SizeChanged:Void()
+	Field Rendered:Void()
+	
 	Method New( title:String,rect:Recti,flags:WindowFlags,jobj:JsonObject )
 		Super.New( title,rect,flags )
 		
@@ -52,7 +55,15 @@ Class MainWindowInstance Extends Window
 			If IsTmpPath( doc.Path ) DeleteFile( doc.Path )
 			SaveState()
 		End
-
+		
+		'IRC tab
+		_ircView=New IRCView
+		_ircView.introScreen.Text="Get live help from other Monkey 2 users"
+		_ircView.introScreen.OnNickChange+=Lambda( nick:String )
+			Prefs.IrcNickname=nick
+		End
+		SetupChatTab()
+		
 		'Build tab
 		
 		_buildConsole=New ConsoleExt
@@ -174,7 +185,7 @@ Class MainWindowInstance Extends Window
 		_findActions=New FindActions( _docsManager,_projectView,_findConsole )
 		_helpActions=New HelpActions
 		_viewActions=New ViewActions( _docsManager )
-
+		
 		_tabMenu=New Menu
 		_tabMenu.AddAction( _fileActions.close )
 		_tabMenu.AddAction( _fileActions.closeOthers )
@@ -299,8 +310,6 @@ Class MainWindowInstance Extends Window
 		_buildMenu.AddAction( _buildActions.lockBuildFile )
 		_buildMenu.AddSeparator()
 		_buildMenu.AddAction( _buildActions.updateModules )
-		_buildMenu.AddAction( _buildActions.rebuildModules )
-		_buildMenu.AddSeparator()
 		_buildMenu.AddAction( _buildActions.moduleManager )
 		
 		'Window menu
@@ -322,6 +331,7 @@ Class MainWindowInstance Extends Window
 		_helpMenu=New MenuExt( "Help" )
 		_helpMenu.AddAction( _helpActions.quickHelp )
 		_helpMenu.AddAction( _helpActions.viewManuals )
+		If IsBananasShowcaseAvailable() Then _helpMenu.AddAction( _helpActions.bananas )
 		_helpMenu.AddSeparator()
 		_helpMenu.AddAction( _buildActions.rebuildHelp )
 		_helpMenu.AddSeparator()
@@ -355,6 +365,7 @@ Class MainWindowInstance Extends Window
 		_consolesTabView.AddTab( "Output",_outputConsoleView,False )
 		_consolesTabView.AddTab( "Docs",_helpConsole,False )
 		_consolesTabView.AddTab( "Find",_findConsole,False )
+		_consolesTabView.AddTab( "Chat",_ircView,False )
 		
 		_statusBar=New StatusBarView
 		
@@ -373,12 +384,22 @@ Class MainWindowInstance Extends Window
 		
 		App.Idle+=OnAppIdle
 		
-		If GetFileType( "bin/ted2.state.json" )=FileType.None _helpActions.about.Trigger()
+		CheckFirstStart()
 		
 		_enableSaving=True
 		
 	End
 	
+	Field PrefsChanged:Void()
+	Method OnPrefsChanged()
+		
+		ArrangeElements()
+		PrefsChanged()
+		
+		SetupChatTab()
+		
+	End
+	
 	Method ArrangeElements()
 		
 		_contentView.RemoveView( _toolBar )
@@ -396,14 +417,15 @@ Class MainWindowInstance Extends Window
 		Local location:=Prefs.MainProjectTabsRight ? "right" Else "left"
 		
 		Local size:=_browsersTabView.Rect.Width
-		If size=0 Then size=250
+		If size=0 Then size=300
 		_contentView.AddView( _browsersTabView,location,size,True )
 		
 		size=_consolesTabView.Rect.Height
-		If size=0 Then size=200
+		If size=0 Then size=150
 		_contentView.AddView( _consolesTabView,"bottom",size,True )
 		
 		_contentView.ContentView=_docsTabView
+		
 	End
 	
 	
@@ -441,7 +463,7 @@ Class MainWindowInstance Extends Window
 		Return _modsDir
 	End
 	
-	Property OverrideTextMode:Bool()
+	Property OverwriteTextMode:Bool()
 	
 		Return _ovdMode
 	Setter( value:Bool )
@@ -476,21 +498,21 @@ Class MainWindowInstance Extends Window
 	
 	Property AboutPagePath:String()
 		
-		Local path:=Prefs.MonkeyRootPath+"About.html"
-		If Not IsFileExists( path )
-			path="asset::ted2/about.html"
-		Endif
+		Local path:=Prefs.MonkeyRootPath+"ABOUT.HTML"
+'		If Not IsFileExists( path )
+'			path="asset::ted2/about.html"
+'		Endif
 		Return path
 	End
 	
 	Method Terminate()
 	
 		_isTerminating=True
-		
 		SaveState()
-		
-		_fileActions.closeAll.Trigger() 'stops all parser's timers on close docs
-		
+		_enableSaving=False
+		OnForceStop() ' kill build process if started
+		ProcessReader.StopAll()
+
 		App.Terminate()
 	End
 
@@ -676,6 +698,15 @@ Class MainWindowInstance Extends Window
 		
 	End
 	
+	Method CheckFirstStart()
+		
+		If GetFileType( "bin/ted2.state.json" )=FileType.None
+			_helpActions.about.Trigger()
+			ShowBananasShowcase()
+		Endif
+	End
+	
+	
 	Public
 	
 	Method ShowProjectView()
@@ -785,6 +816,10 @@ Class MainWindowInstance Extends Window
 		_helpTree.Update()
 	End
 	
+	Method ShowBananasShowcase()
+		OpenDocument( Prefs.MonkeyRootPath+"bananas/!showcase/all.bananas" )
+	End
+	
 	Method ReadError( path:String )
 		Alert( "I/O Error reading file '"+path+"'" )
 	End
@@ -893,9 +928,10 @@ Class MainWindowInstance Extends Window
 		SaveString( jobj.ToJson(),"bin/ted2.state.json" )
 	End
 
-	Method OpenDocument( path:String )
+	Method OpenDocument( path:String,lockIt:Bool=False )
 	
 		_docsManager.OpenDocument( path,True )
+		If lockIt Then _buildActions.LockBuildFile()
 	End
 	
 	Method GetActionFind:Action()
@@ -925,7 +961,16 @@ Class MainWindowInstance Extends Window
 			_inited=True
 			OnInit()
 		Endif
+		
 		Super.OnRender( canvas )
+		
+		If _resized
+			_resized=False
+			SizeChanged()
+		Endif
+		
+		Rendered()
+		Rendered=Null
 	End
 	
 	Method OnInit()
@@ -934,15 +979,34 @@ Class MainWindowInstance Extends Window
 		_docsTabView.EnsureVisibleCurrentTab()
 	End
 	
-	Method OnCloseApp()
+	Method OnAppClose()
 		
-		SaveState()
-		_enableSaving=False
-		OnForceStop() ' kill build process if started
-		ProcessReader.StopAll()
 		_fileActions.quit.Trigger()
 	End
 	
+	Method OnResized()
+		
+		' just set a flag here.
+		' SizeChanged event will be called inside of OnRender to take re-layout effect.
+		_resized=True
+	End
+	
+	Method SetupChatTab()
+		
+		If Not _ircView Return
+		
+		Local intro:=_ircView.introScreen
+		
+		If intro.IsConnected Return
+		
+		Local nick:=Prefs.IrcNickname
+		Local server:=Prefs.IrcServer
+		Local port:=Prefs.IrcPort
+		Local rooms:=Prefs.IrcRooms
+		intro.AddOnlyServer( nick,server,server,port,rooms )
+		
+	End
+	
 	Method LoadState( jobj:JsonObject )
 	
 		If jobj.Contains( "browserSize" )
@@ -1025,12 +1089,17 @@ Class MainWindowInstance Extends Window
 	Method OnWindowEvent( event:WindowEvent ) Override
 
 		Select event.Type
-		Case EventType.WindowClose
-			OnCloseApp()
-		Default
-			Super.OnWindowEvent( event )
+			
+			Case EventType.WindowClose
+				OnAppClose()
+			
+			Case EventType.WindowResized
+				OnResized()
+			
+			Default
+				Super.OnWindowEvent( event )
+			
 		End
-	
 	End
 	
 	
@@ -1050,6 +1119,7 @@ Class MainWindowInstance Extends Window
 	Field _helpActions:HelpActions
 	Field _viewActions:ViewActions
 	
+	Field _ircView:IRCView
 	Field _buildConsole:ConsoleExt
 	Field _outputConsole:ConsoleExt
 	Field _outputConsoleView:DockingView
@@ -1061,7 +1131,8 @@ Class MainWindowInstance Extends Window
 	Field _docBrowser:DockingView
 	Field _debugView:DebugView
 	Field _helpTree:HelpTreeView
-
+	
+	'Field _ircTabView:TabView
 	Field _docsTabView:TabViewExt
 	Field _consolesTabView:TabView
 	Field _browsersTabView:TabView
@@ -1100,9 +1171,9 @@ Class MainWindowInstance Extends Window
 	Field _consoleVisibleCounter:=0
 	Field _isTerminating:Bool
 	Field _enableSaving:Bool
+	Field _resized:Bool
 	Field _browsersSize:=0,_consolesSize:=0
-	
-	
+
 	
 	Method ToJson:JsonValue( rect:Recti )
 		Return New JsonArray( New JsonValue[]( New JsonNumber( rect.min.x ),New JsonNumber( rect.min.y ),New JsonNumber( rect.max.x ),New JsonNumber( rect.max.y ) ) )

+ 52 - 22
Prefs.monkey2

@@ -16,6 +16,12 @@ Class Prefs
 	'
 	Global MainToolBarVisible:=True
 	Global MainProjectTabsRight:=True
+	Global MainProjectIcons:=True
+	'
+	Global IrcNickname:String
+	Global IrcServer:="irc.freenode.net"
+	Global IrcPort:=6667
+	Global IrcRooms:="#monkey2" '#monkey2Ui#monkey23D
 	'
 	Global EditorToolBarVisible:=False
 	Global EditorGutterVisible:=True
@@ -23,6 +29,8 @@ Class Prefs
 	Global EditorFontPath:String
 	Global EditorFontSize:=16
 	Global EditorShowEvery10LineNumber:=True
+	Global EditorCodeMapVisible:=True
+	Global EditorAutoIndent:=True
 	'
 	Global SourceSortByType:=True
 	Global SourceShowInherited:=False
@@ -31,12 +39,22 @@ Class Prefs
 	
 	Function LoadState( json:JsonObject )
 		
+		If json.Contains( "irc" )
+			
+			Local j2:=json["irc"].ToObject()
+			IrcNickname=Json_GetString( j2,"nickname",IrcNickname )
+			IrcServer=Json_GetString( j2,"server",IrcServer )
+			IrcPort=Json_GetInt( j2,"port",IrcPort )
+			IrcRooms=Json_GetString( j2,"rooms",IrcRooms )
+		Endif
+		
 		If json.Contains( "main" )
 			
 			Local j2:=json["main"].ToObject()
-			If j2.Contains( "toolBarVisible" ) Then MainToolBarVisible=j2["toolBarVisible"].ToBool()
-			If j2.Contains( "tabsRight" ) Then MainProjectTabsRight=j2["tabsRight"].ToBool()
-		
+			MainToolBarVisible=Json_GetBool( j2,"toolBarVisible",MainToolBarVisible )
+			MainProjectTabsRight=Json_GetBool( j2,"tabsRight",MainProjectTabsRight )
+			MainProjectIcons=Json_GetBool( j2,"projectIcons",MainProjectIcons )
+      
 		Endif
 		
 		If json.Contains( "completion" )
@@ -47,9 +65,9 @@ Class Prefs
 			AcShowAfter=j2["showAfter"].ToNumber()
 			AcUseTab=j2["useTab"].ToBool()
 			AcUseEnter=j2["useEnter"].ToBool()
-			AcUseSpace=GetJsonBool( j2,"useSpace",AcUseSpace )
-			AcUseDot=GetJsonBool( j2,"useDot",AcUseDot )
-			AcNewLineByEnter=j2["newLineByEnter"].ToBool()
+			AcUseSpace=Json_GetBool( j2,"useSpace",AcUseSpace )
+			AcUseDot=Json_GetBool( j2,"useDot",AcUseDot )
+			AcNewLineByEnter=Json_GetBool( j2,"newLineByEnter",AcNewLineByEnter )
 			
 		Endif
 		
@@ -58,10 +76,12 @@ Class Prefs
 			Local j2:=json["editor"].ToObject()
 			EditorToolBarVisible=j2["toolBarVisible"].ToBool()
 			EditorGutterVisible=j2["gutterVisible"].ToBool()
-			EditorShowWhiteSpaces=GetJsonBool( j2,"showWhiteSpaces",EditorShowWhiteSpaces )
-			If j2.Contains("fontPath") Then EditorFontPath=j2["fontPath"].ToString()
-			If j2.Contains("fontSize") Then EditorFontSize=Int( j2["fontSize"].ToNumber() )
-			EditorShowEvery10LineNumber=GetJsonBool( j2,"showEvery10",EditorShowEvery10LineNumber )
+			EditorShowWhiteSpaces=Json_GetBool( j2,"showWhiteSpaces",EditorShowWhiteSpaces )
+			EditorFontPath=Json_GetString( j2,"fontPath", EditorFontPath )
+			EditorFontSize=Json_GetInt( j2,"fontSize",EditorFontSize )
+			EditorShowEvery10LineNumber=Json_GetBool( j2,"showEvery10",EditorShowEvery10LineNumber )
+			EditorCodeMapVisible=Json_GetBool( j2,"codeMapVisible",EditorCodeMapVisible )
+			EditorAutoIndent=Json_GetBool( j2,"autoIndent",EditorAutoIndent )
 			
 		Endif
 		
@@ -77,11 +97,20 @@ Class Prefs
 	Function SaveState( json:JsonObject )
 		
 		Local j:=New JsonObject
+		json["main"]=j
 		j["toolBarVisible"]=New JsonBool( MainToolBarVisible )
 		j["tabsRight"]=New JsonBool( MainProjectTabsRight )
-		json["main"]=j
-		 
+		j["projectIcons"]=New JsonBool( MainProjectIcons )
+		
 		j=New JsonObject
+		json["irc"]=j
+		j["nickname"]=New JsonString( IrcNickname )
+		j["server"]=New JsonString( IrcServer )
+		j["port"]=New JsonNumber( IrcPort )
+		j["rooms"]=New JsonString( IrcRooms )
+		
+		j=New JsonObject
+		json["completion"]=j
 		j["enabled"]=New JsonBool( AcEnabled )
 		j["keywordsOnly"]=New JsonBool( AcKeywordsOnly )
 		j["showAfter"]=New JsonNumber( AcShowAfter )
@@ -90,21 +119,23 @@ Class Prefs
 		j["useSpace"]=New JsonBool( AcUseSpace )
 		j["useDot"]=New JsonBool( AcUseDot )
 		j["newLineByEnter"]=New JsonBool( AcNewLineByEnter )
-		json["completion"]=j
 		
 		j=New JsonObject
+		json["editor"]=j
 		j["toolBarVisible"]=New JsonBool( EditorToolBarVisible )
 		j["gutterVisible"]=New JsonBool( EditorGutterVisible )
 		j["showWhiteSpaces"]=New JsonBool( EditorShowWhiteSpaces )
 		j["fontPath"]=New JsonString( EditorFontPath )
 		j["fontSize"]=New JsonNumber( EditorFontSize )
 		j["showEvery10"]=New JsonBool( EditorShowEvery10LineNumber )
-		json["editor"]=j
+		j["codeMapVisible"]=New JsonBool( EditorCodeMapVisible )
+		j["autoIndent"]=New JsonBool( EditorAutoIndent )
 		
 		j=New JsonObject
+		json["source"]=j
 		j["sortByType"]=New JsonBool( SourceSortByType )
 		j["showInherited"]=New JsonBool( SourceShowInherited )
-		json["source"]=j
+		
 	End
 	
 	Function LoadLocalState()
@@ -112,12 +143,14 @@ Class Prefs
 		Local json:=JsonObject.Load( AppDir()+"state.json" )
 		If Not json Return
 		
-		If json.Contains( "rootPath" ) Then MonkeyRootPath=json["rootPath"].ToString()
-		
+		MonkeyRootPath=Json_GetString( json.Data,"rootPath","" )
+		If Not MonkeyRootPath.EndsWith( "/" ) Then MonkeyRootPath+="/"
 	End
 	
 	Function SaveLocalState()
 		
+		If Not MonkeyRootPath.EndsWith( "/" ) Then MonkeyRootPath+="/"
+		
 		Local json:=New JsonObject
 		json["rootPath"]=New JsonString( MonkeyRootPath )
 		json.Save( AppDir()+"state.json" )
@@ -139,12 +172,9 @@ Class Prefs
 	
 	Function GetCustomFontSize:Int()
 	
-		Return Max( EditorFontSize,6 ) '6 is a minumal
+		Return Max( EditorFontSize,6 ) '6 is a minimum
 	End
 End
 
 
-Function GetJsonBool:Bool( json:Map<String,JsonValue>,key:String,def:Bool )
-	
-	Return json[key] ? json[key].ToBool() Else def
-End
+

+ 11 - 2
ProcessReader.monkey2

@@ -21,6 +21,10 @@ Both methods are using Fiber to waiting process finished.
 #end
 Class ProcessReader
 	
+	#rem monkeydoc Tag to identify reader.
+	#end
+	Field Tag:=""
+	
 	#rem monkeydoc Invoked when a process finishes execution.
 	#end
 	Field Finished:Void( output:String,exitCode:Int )
@@ -35,7 +39,7 @@ Class ProcessReader
 	
 	#rem monkeydoc Obtain a reader instance.
 	#end
-	Function Obtain:ProcessReader()
+	Function Obtain:ProcessReader( tag:String="" )
 	
 		Local r:ProcessReader
 		If _recycled.Empty
@@ -47,6 +51,7 @@ Class ProcessReader
 		r.Finished=Null
 		r.PortionRead=Null
 		r.Error=Null
+		r.Tag=tag
 		_items.Add( r )
 		Return r
 	End
@@ -100,7 +105,10 @@ Class ProcessReader
 	#end
 	Method Stop()
 	
-		If _running Then _process.Terminate()
+		If Not _procOpen Return
+		
+		_process.Terminate()
+		_running=False
 	End
 	
 	#rem monkeydoc Is reading currently in progress.
@@ -190,6 +198,7 @@ Class ProcessReader
 		
 		Finished( _output,code )
 		If code<>0 Then Error( code )
+		
 	End
 	
 End

+ 73 - 21
Ted2.monkey2

@@ -33,6 +33,7 @@
 #Import "dialog/DialogExt"
 #Import "dialog/NoTitleDialog"
 #Import "dialog/FindInFilesDialog"
+#Import "dialog/UpdateModulesDialog"
 
 #Import "document/DocumentManager"
 #Import "document/Ted2Document"
@@ -42,6 +43,7 @@
 #Import "document/AudioDocument"
 #Import "document/JsonDocument"
 #Import "document/XmlDocument"
+#Import "document/BananasDocument"
 
 #Import "eventfilter/TextViewKeyEventFilter"
 #Import "eventfilter/Monkey2KeyEventFilter"
@@ -71,12 +73,15 @@
 #Import "utils/JsonUtils"
 #Import "utils/Utils"
 
+#Import "view/IRCView"
+#Import "view/CodeMapView"
 #Import "view/CodeTextView"
 #Import "view/ConsoleViewExt"
 #Import "view/ListViewExt"
 #Import "view/AutocompleteView"
 #Import "view/CodeTreeView"
 #Import "view/TreeViewExt"
+#Import "view/FileBrowserExt"
 #Import "view/CodeGutterView"
 #Import "view/ToolBarViewExt"
 #Import "view/HintView"
@@ -93,6 +98,7 @@
 #Import "view/Monkey2TreeView"
 #Import "view/GutterView"
 #Import "view/MenuExt"
+#Import "view/ScrollableViewExt"
 
 #Import "MainWindow"
 #Import "Plugin"
@@ -111,7 +117,7 @@ Using tinyxml2..
 
 Const MONKEY2_DOMAIN:="http://monkeycoder.co.nz"
 
-Global AppTitle:="Ted2Go v2.3.2"
+Global AppTitle:="Ted2Go v2.4"
 
 
 Function Main()
@@ -141,7 +147,7 @@ Function Main()
 	Prefs.LoadState( jobj )
 	
 	'initial theme
-	'	
+	'
 	If Not jobj.Contains( "theme" ) jobj["theme"]=New JsonString( "theme-classic-dark" )
 	If Not jobj.Contains( "themeScale" ) jobj["themeScale"]=New JsonNumber( 1 )
 	
@@ -151,7 +157,7 @@ Function Main()
 	config["initialThemeScale"]=jobj.GetNumber( "themeScale" )
 	
 	'start the app!
-	'	
+	'
 	New AppInstance( config )
 	
 	'initial window state
@@ -163,8 +169,8 @@ Function Main()
 	If jobj.Contains( "windowRect" ) 
 		rect=ToRecti( jobj["windowRect"] )
 	Else
-		Local w:=Min( 1024,App.DesktopSize.x-40 )
-		Local h:=Min( 768,App.DesktopSize.y-64 )
+		Local w:=Min( 1380,App.DesktopSize.x-40 )
+		Local h:=Min( 970,App.DesktopSize.y-64 )
 		rect=New Recti( 0,0,w,h )
 		flags|=WindowFlags.Center
 	Endif
@@ -182,7 +188,7 @@ Function Main()
 	Next
 	
 	App.Run()
-		
+	
 End
 
 Function SetupMonkeyRootPath:String( rootPath:String,searchMode:Bool )
@@ -192,23 +198,22 @@ Function SetupMonkeyRootPath:String( rootPath:String,searchMode:Bool )
 	ChangeDir( rootPath )
 	
 	If searchMode
-		While GetFileType( "bin" )<>FileType.Directory Or GetFileType( "modules" )<>FileType.Directory
-	
-			If IsRootDir( CurrentDir() )
-				
-				Local ok:=Confirm( "Initializing","Error initializing - can't find working dir!~nDo you want to specify Monkey2 root folder now?" )
-				If Not ok
-					Return ""
-				End
-				Local s:=requesters.RequestDir( "Choose Monkey2 folder",AppDir() )
-				ChangeDir( s )
-				Continue
-			Endif
-			
-			ChangeDir( ExtractDir( CurrentDir() ) )
+		' search for desired folder
+		Local found:=FindBinFolder( rootPath )
+		' search for AddDir() folder
+		If Not found And rootPath<>AppDir() Then found=FindBinFolder( AppDir() )
+		' search for choosen-by-requester folder
+		While Not found
+	
+			Local ok:=Confirm( "Initializing","Error initializing - can't find working dir!~nDo you want to specify Monkey2 root folder now?" )
+			If Not ok
+				Return ""
+			End
+			Local s:=requesters.RequestDir( "Choose Monkey2 folder",AppDir() )
+			found=FindBinFolder( s )
 		Wend
 		
-		rootPath=CurrentDir()
+		rootPath=found
 	Else
 		
 		Local ok:= (GetFileType( "bin" )=FileType.Directory And GetFileType( "modules" )=FileType.Directory)
@@ -233,3 +238,50 @@ Function IsFileExists:Bool( path:String )
 	
 	Return GetFileType( path ) = FileType.File
 End
+
+Function Exec( exePath:String,args:String="" )
+
+#If __HOSTOS__="windows"
+
+	libc.system( exePath+" "+args )
+	
+#Else If __HOSTOS__="macos"
+
+	libc.system( "open ~q"+exePath+"~q --args "+args )
+
+#Else If __HOSTOS__="linux"
+
+	libc.system( exePath+" "+args+" >/dev/null 2>/dev/null &" )
+
+#Else If __HOSTOS__="raspbian"
+
+	libc.system( exePath+" "+args+" >/dev/null 2>/dev/null &" )
+
+#Endif
+
+End
+
+
+Private
+
+Function FindBinFolder:String( startingFolder:String )
+	
+	Local cur:=CurrentDir()
+	Local ok:=True
+	ChangeDir( startingFolder )
+	
+	While GetFileType( "bin" )<>FileType.Directory Or GetFileType( "modules" )<>FileType.Directory
+	
+		If IsRootDir( CurrentDir() )
+			
+			ok=False
+			Exit
+		Endif
+	
+		ChangeDir( ExtractDir( CurrentDir() ) )
+	Wend
+	Local result:=ok ? CurrentDir() Else ""
+	ChangeDir( cur )
+	
+	Return result
+End

+ 45 - 58
action/BuildActions.monkey2

@@ -26,7 +26,7 @@ End
 
 Interface IModuleBuilder
 	
-	Method BuildModules:Bool( clean:Bool,modules:String="" )
+	Method BuildModules:Bool( clean:Bool,modules:String="",configs:String="debug release" )
 	
 End
 
@@ -41,7 +41,6 @@ Class BuildActions Implements IModuleBuilder
 	Field nextError:Action
 	Field lockBuildFile:Action
 	Field updateModules:Action
-	Field rebuildModules:Action
 	Field moduleManager:Action
 	Field rebuildHelp:Action
 	
@@ -110,16 +109,11 @@ Class BuildActions Implements IModuleBuilder
 		lockBuildFile.HotKey=Key.L
 		lockBuildFile.HotKeyModifiers=Modifier.Menu
 		
-		updateModules=New Action( "Update modules..." )
+		updateModules=New Action( "Update / Rebuild modules..." )
 		updateModules.Triggered=OnUpdateModules
 		updateModules.HotKey=Key.U
 		updateModules.HotKeyModifiers=Modifier.Menu
 		
-		rebuildModules=New Action( "Rebuild modules..." )
-		rebuildModules.Triggered=OnRebuildModules
-		rebuildModules.HotKey=Key.U
-		rebuildModules.HotKeyModifiers=Modifier.Menu|Modifier.Shift
-		
 		moduleManager=New Action( "Module manager..." )
 		moduleManager.Triggered=OnModuleManager
 		
@@ -220,6 +214,11 @@ Class BuildActions Implements IModuleBuilder
 		Return _locked
 	End
 	
+	Method LockBuildFile()
+		
+		OnLockBuildFile()
+	End
+	
 	Method SaveState( jobj:JsonObject )
 		
 		If _locked jobj["lockedDocument"]=New JsonString( _locked.Path )
@@ -288,50 +287,42 @@ Class BuildActions Implements IModuleBuilder
 		buildAndRun.Enabled=canbuild
 		nextError.Enabled=Not _errors.Empty
 		updateModules.Enabled=idle
-		rebuildModules.Enabled=idle
 		rebuildHelp.Enabled=idle
 		moduleManager.Enabled=idle
 	End
 
-	Method BuildModules:Bool( clean:Bool,modules:String="" )
-	
-		Local targets:=New StringStack
+	Method BuildModules:Bool( clean:Bool,modules:String="",configs:String="debug release" )
 	
-		For Local target:=Eachin _validTargets
-			targets.Push( target="ios" ? "iOS" Else target.Capitalize() )
+		Local dialog:=New UpdateModulesDialog( _validTargets,modules,configs,clean )
+		dialog.Title="Update / Rebuild modules"
+		
+		Local ok:=dialog.ShowModal()
+		If Not ok Return False
+		
+		Local result:=True
+		
+		Local targets:=dialog.SelectedTargets
+		modules=dialog.SelectedModules
+		clean=dialog.NeedClean
+		configs=dialog.SelectedConfigs
+		
+		Local time:=Millisecs()
+		
+		For Local target:=Eachin targets
+			result=BuildModules( clean,target,modules,configs )
+			If result=False Exit
 		Next
-	
-		targets.Push( "All!" )
-		targets.Push( "Cancel" )
-	
+		
+		time=Millisecs()-time
 		Local prefix:=clean ? "Rebuild" Else "Update"
-		Local text:="Modules: "
-		If modules Then text+=modules.Replace( " ",", " ) Else text+="All"
-		text+="~n~nSelect target:"
-	
-		Local i:=TextDialog.Run( prefix+" modules",text,targets.ToArray(),0,targets.Length-1 )
-	
-		Local result:=True
-	
-		Select i
-		Case targets.Length-1	'Cancel
-			Return False
-		Case targets.Length-2	'All!
-			For Local i:=0 Until targets.Length-2
-				If BuildModules( clean,targets[i],modules ) Continue
-				result=False
-				Exit
-			Next
-		Default
-			result=BuildModules( clean,targets[i],modules )
-		End
-	
+		
 		If result
 			_console.Write( "~n"+prefix+" modules completed successfully!~n" )
 		Else
 			_console.Write( "~n"+prefix+" modules failed.~n" )
 		Endif
-	
+		_console.Write( "Total time elapsed: "+FormatTime( time )+".~n" )
+		
 		Return result
 	End
 	
@@ -360,6 +351,7 @@ Class BuildActions Implements IModuleBuilder
 	Field _validTargets:StringStack
 	Field _timing:Long
 	
+	
 	Method BuildDoc:CodeDocument()
 		
 		If Not _locked Return Cast<CodeDocument>( _docs.CurrentDocument )
@@ -400,7 +392,7 @@ Class BuildActions Implements IModuleBuilder
 		tv.GotoLine( err.line )
 	End
 	
-	Method BuildMx2:Bool( cmd:String,progressText:String,action:String="build" )
+	Method BuildMx2:Bool( cmd:String,progressText:String,action:String="build",showElapsedTime:Bool=False )
 	
 		ClearErrors()
 		
@@ -478,23 +470,24 @@ Class BuildActions Implements IModuleBuilder
 		Local status:=hasErrors ? "{0} failed. See the build console for details." Else (_console.ExitCode=0 ? "{0} finished." Else "{0} cancelled.")
 		status=status.Replace( "{0}",title )
 		
-		Local elapsed:=(Millisecs()-_timing)/1000
-		Local m:=elapsed/60
-		Local sec:=elapsed Mod 60
-		status+="   Time elapsed: "+m+" m "+sec+" s."
+		If showElapsedTime
+			Local elapsed:=(Millisecs()-_timing)
+			status+="   Time elapsed: "+FormatTime( elapsed )+"."
+		Endif
 		
 		MainWindow.ShowStatusBarText( status )
 		
 		Return _console.ExitCode=0
 	End
 
-	Method BuildModules:Bool( clean:Bool,target:String,modules:String )
-	
+	Method BuildModules:Bool( clean:Bool,target:String,modules:String,configs:String="debug release" )
+		
 		Local msg:=(clean ? "Rebuilding ~ " Else "Updating ~ ")+target
 		
-		For Local config:=0 Until 2
+		Local arr:=configs.Split( " " )
+		For Local cfg:=Eachin arr
 		
-			Local cfg:=(config ? "debug" Else "release")
+			'Local cfg:=(config ? "debug" Else "release")
 			
 			Local cmd:=MainWindow.Mx2ccPath+" makemods -target="+target
 			If clean cmd+=" -clean"
@@ -512,7 +505,7 @@ Class BuildActions Implements IModuleBuilder
 	
 	Method MakeDocs:Bool()
 	
-		Return BuildMx2( MainWindow.Mx2ccPath+" makedocs","Rebuilding documentation..." )
+		Return BuildMx2( MainWindow.Mx2ccPath+" makedocs","Rebuilding documentation...","build",True )
 	End
 	
 	Method BuildApp:Bool( config:String,target:String,sourceAction:String )
@@ -538,7 +531,7 @@ Class BuildActions Implements IModuleBuilder
 		Local title := sourceAction="build" ? "Building" Else (sourceAction="run" ? "Running" Else "Checking")
 		Local msg:=title+" ~ "+target+" ~ "+config+" ~ "+StripDir( buildDoc.Path )
 		
-		If Not BuildMx2( cmd,msg,sourceAction ) Return False
+		If Not BuildMx2( cmd,msg,sourceAction,True ) Return False
 		
 		_console.Write("~nDone.")
 		
@@ -619,6 +612,7 @@ Class BuildActions Implements IModuleBuilder
 	Method OnLockBuildFile()
 	
 		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
+		
 		If Not doc Return
 		
 		If _locked _locked.State=""
@@ -648,13 +642,6 @@ Class BuildActions Implements IModuleBuilder
 		BuildModules( False )
 	End
 	
-	Method OnRebuildModules()
-	
-		If _console.Running Return
-	
-		BuildModules( True )
-	End
-	
 	Method OnModuleManager()
 	
 		If _console.Running Return

+ 8 - 1
action/EditActions.monkey2

@@ -125,7 +125,14 @@ Class EditActions
 	
 		Local tv:=Cast<TextView>( App.KeyView )
 		
-		If tv tv.WordWrap=Not tv.WordWrap
+		If tv
+			Local cur:=tv.Cursor
+			Local anc:=tv.Anchor
+			Local sc:=tv.Scroll
+			tv.WordWrap=Not tv.WordWrap
+			tv.SelectText( cur,anc )
+			tv.Scroll=sc
+		Endif
 	End
 	
 End

+ 1 - 1
action/FileActions.monkey2

@@ -345,7 +345,7 @@ Class FileActions
 					If tv Then tv.UpdatePrefs()
 				Next
 				
-				MainWindow.ArrangeElements()
+				MainWindow.OnPrefsChanged()
 			End
 			
 		Endif

+ 7 - 0
action/HelpActions.monkey2

@@ -12,6 +12,7 @@ Class HelpActions
 	Field aboutTed2go:Action
 	Field makeBetter:Action
 	Field mx2homepage:Action
+	Field bananas:Action
 	
 
 	Method New()
@@ -83,6 +84,12 @@ Class HelpActions
 		
 			OpenUrl( MONKEY2_DOMAIN )
 		End
+		
+		bananas=New Action( "Bananas showcase" )
+		bananas.Triggered=lambda()
+		
+			MainWindow.ShowBananasShowcase()
+		End
 	End
 
 	

BIN
assets/fonts/MesloLGSDZ-Bold.ttf


+ 3 - 1
assets/themes/default.json → assets/themes/default-tmp.json

@@ -334,6 +334,8 @@
 		
 		"DialogActions":{
 			"padding":[ 8,4,8,4 ]
-		}
+		},
+
+		"GridView":{}
 	}
 }

BIN
assets/themes/irc/notice.png


BIN
assets/themes/prime_assets/button_skin.png


BIN
assets/themes/prime_assets/checkbox_icons.png


BIN
assets/themes/prime_assets/dialog_skin.png


BIN
assets/themes/prime_assets/progressbar_icons.png


BIN
assets/themes/prime_assets/square.png


BIN
assets/themes/prime_assets/tabbutton_skin.png


BIN
assets/themes/prime_assets/tabclose_icons.png


BIN
assets/themes/prime_assets/treeview_icons.png


+ 50 - 8
assets/themes/ted2-default.json

@@ -3,10 +3,22 @@
 	
 	"colors":{
 
+		"transparent": "#0000",
+		"textview-cursor-line":"#2000",
+		"textview-whitespaces":"#2aaa",
 		"statusbar": "content",
-		"statusbar-active": "#CA5100"
+		"statusbar-active": "#CA5100",
+		"menu-shortcut":"text-background",
+		"codemap-background": "transparent",
+		"codemap-selection": "#48000000"
 	},
 
+	"fonts":{
+	
+		"editor":"DejaVuSansMono.ttf,14",
+		"bananas-title":"DejaVuSansMono.ttf,20"
+	},
+	
 	"styles":{
 	
 		"GutterView":{
@@ -96,6 +108,11 @@
 			"extends":"ProgressBar",
 			"margin":[ 6,0 ]
 		},
+		"StatusBarButton":{
+			"extends":"ToolButton",
+			"padding":[ 0 ],
+			"skinColor":"transparent"
+		},
 
 		"CompletionDialog":{
 			"extends":"Dialog"
@@ -109,17 +126,42 @@
 			"backgroundColor":"content"
 		},
 		
-		"StatusBarButton":{
-			"extends":"ToolButton",
-			"padding":[ 0 ],
-			"skinColor":"transparent"
-		},
-		
 		"TextFieldBordered":{
 			"extends":"TextField",
 			"border":[ 1 ],
 			"borderColor":"clear"
-		}
+		},
 		
+		"CodeMapView":{
+			"extends":"TextView",
+			"margin":[ 4,0,0,0 ],
+			"padding":[ 0 ],
+			"border":[ 1,0,0,0 ],
+			"borderColor": "clear",
+			"backgroundColor":"codemap-background"
+		},
+
+		"BananasView":{
+			"border":[ 0 ],
+			"padding":[ 0 ],
+			"backgroundColor":"content"
+		},
+		"BananasCard":{
+			"margin":[ 10,15,10,15 ],
+			"border":[ 1 ],
+			"borderColor":"clear"
+		},
+		"BananasTitle":{
+			"extends": "Label",
+			"font": "bananas-title"
+		},
+		"BananasButton":{
+			"extends": "Button",
+			"margin": [ 4,0,0,0 ]
+		},
+		"BananasDescription":{
+			"extends":"TextView",
+			"font":"normal"
+		}
 	}
 }

+ 455 - 0
assets/themes/theme-prime-base.json

@@ -0,0 +1,455 @@
+//Theme by @Hezkore
+{
+	"extends":"ted2-default",
+	
+	"fonts":{
+	
+		"normal":"Roboto-Medium.ttf,16",
+		"fixedWidth":"RobotoMono-Medium.ttf,18",
+		"small":"Roboto-Medium.ttf,13",
+		"editor":"MesloLGSDZ-Bold.ttf,18"
+	},
+
+	"colors":{
+
+		// "accent": "",
+		// "accentDark": ""
+
+		"transparent": "#0000",
+		"darkest": "#010101",
+		"darker": "#171717",
+		"dark": "#1A1A1A",
+		"bright": "#222222",
+		"brighter": "#2D2D2D",
+		"brightest": "#333333",
+		
+		"text-default": "#AAAAAA",
+		"text-highlight": "#FFFFFF",
+		"text-disabled": "#555555",
+		"text-background": "#888",
+		
+		"textview-cursor":"#fff",
+		"textview-selection":"accentDark",
+		"textview-cursor-line":"#2A2A2A",
+		"textview-whitespaces":"#2A2A2A",
+
+		"textview-color0":"#FF00FF", 	//When is this used?
+		"textview-color1": "#ECF0F1",	//identifiers
+		"textview-color2": "#2ECC71",	//keywords
+		"textview-color3": "#F1C410",	//strings
+		"textview-color4": "#6C71C4",	//numbers
+		"textview-color5": "#606060",	//comment
+		"textview-color6": "#E74C31",	//preproc
+		"textview-color7": "#FF00FF",	//When is this used?
+		
+		"windowClearColor":"bright",
+		"menu-shortcut":"text-background",
+
+		"statusbar": "#010101",
+		"statusbar-active": "accentDark"
+	},
+	
+	"styles":{
+
+		"default":{
+			"font":"normal",
+			"textColor":"text-default",
+			"iconColor":"#ffff",
+			"states":{
+				"disabled":{
+					"textColor":"text-disabled",
+					"iconColor":"#8fff"
+				}
+			}
+		},
+
+		"GutterView":{
+			"padding":[ -16,0,16,0 ],
+			"extends":"TextView",
+			"textColor":"text-disabled",
+			"backgroundColor":"bright",
+			"font":"editor"
+		},
+		
+		"DebugToolBar":{
+			"extends":"ToolBar",
+			"border":[ 1 ],
+			"borderColor":"bright",
+			"icons":"debug_icons.png"
+		},
+		
+		"HelpTextField":{
+			"extends":"TextField",
+			"skinColor":"darker"
+		},
+
+		"Hint":{
+			"font":"small",
+			"textColor":"accent",
+			"backgroundColor":"darkest"
+		},
+
+		"ToolBarExt":{
+			"extends":"ToolBar",
+			"padding":[ 0 ],
+			"margin":[ 0 ],
+			"backgroundColor":"dark",
+			"border":[ 0 ]
+		},
+
+		"MainToolBar":{
+			"extends":"ToolBarExt"
+		},
+
+		"EditorToolBar":{
+			"extends":"ToolBarExt"
+		},
+
+		"SourceToolBar":{
+			"extends":"ToolBarExt"
+		},
+
+		"ToolButton":{
+			"extends":"Button",
+			"padding":[ 2,0 ],
+			"margin":[ 2,0 ]
+		},
+
+		"TabViewArrowPrev":{
+			"extends":"Button",
+			"padding":[ 8,4 ],
+			"margin":[ 2,0,2,0 ]
+		},
+
+		"TabViewArrowNext":{
+			"extends":"Button",
+			"padding":[ 8,4 ],
+			"margin":[ 2,0,12,0 ]
+		},
+
+		"ProgressBar":{
+			"iconColor":"accent",
+			"icons":"prime_assets/progressbar_icons.png"
+		},
+
+		"StatusBar":{
+			"extends":"DockingView",
+			"padding":[ 0,8,0,8 ],
+			"backgroundColor":"statusbar"
+		},
+		"StatusBarText":{
+			"extends":"Label",
+			"font":"small"
+		},
+		"StatusBarLineInfo":{
+			"extends":"Label",
+			"margin":[ 40,0,0,0 ],
+			"font":"small"
+		},
+		"StatusBarIns":{
+			"extends":"Label",
+			"font":"small"
+		},
+		"StatusBarProgress":{
+			"extends":"ProgressBar",
+			"margin":[ 6,0 ]
+		},
+
+		"CompletionDialog":{
+			"extends":"Dialog"
+		},
+		"CompletionDialogContent":{
+			"padding":[ 1 ]
+		},
+		
+		"ProjectTabView":{
+			"extends":"TabView",
+			"backgroundColor":"dark"
+		},
+		
+		"StatusBarButton":{
+			"extends":"ToolButton",
+			"padding":[ 0 ],
+			"skinColor":"transparent"
+		},
+		
+		"Label":{
+			"padding":[8,4]
+		},
+		
+		"Button":{
+			"extends":"Label",
+			"padding":[4,2],
+			"skin":"prime_assets/square.png",
+			"skinColor":"darker",
+			
+			"states":{
+				"hover":{
+					"textColor":"accent",
+					"skinColor":"dark"
+				},
+				"active":{
+					"textColor":"accent",
+					"skinColor":"dark"
+				},
+				"selected":{
+					"textColor":"accent",
+					"skinColor":"dark"
+				}
+			}
+		},
+		
+		"PushButton":{
+			"extends":"Button",
+			"margin":[4,4]
+		},
+		
+		"CheckButton":{
+			"extends":"Label"
+		},
+		
+		"CheckBox":{
+			"icons":"prime_assets/checkbox_icons.png",
+			"iconColor":"accent",
+			"margin":[0,0,0,0]
+		},
+		
+		"ScrollView":{
+		},
+		
+		"ScrollBar":{
+			"backgroundColor":"bright"
+		},
+		
+		"ScrollKnob":{
+			"padding":[ 4 ],
+			"border":[ 0 ],
+			"skin":"prime_assets/square.png",
+			"skinColor":"brightest",
+			"states":{
+				"hover":{
+					"skinColor":"brighter"
+				},
+				"active":{
+					"skinColor":"brightest"
+				}
+			}
+		},
+		
+		"TextView":{
+			"font":"editor",
+			"backgroundColor":"dark"
+		},
+		
+		"TextViewContent":{
+			"padding":[4]
+		},
+		
+		"TextField":{
+			"font":"normal",
+			"padding":[ 2 ],
+			"margin":[ 2 ],
+			"skin":"prime_assets/square.png",
+			"skinColor":"darker"
+		},
+		
+		"DockingView":{
+		},
+		
+		"DockedView":{
+		},
+		
+		"DockKnob":{
+			"padding":[ 3 ],
+			"backgroundColor":"darker",
+			"states":{
+				"hover":{
+					"backgroundColor":"accentDark"
+				},
+				"active":{
+					"backgroundColor":"accent"
+				}
+			}
+		},
+		
+		"ToolBar":{
+			"padding":[ 2 ],
+			"backgroundColor":"darker"
+		},
+		
+		"Menu":{
+			"extends":"DockingView",
+			"padding":[ 0 ],
+			"skin":"prime_assets/dialog_skin.png",
+			"skinColor":"darkest"
+		},
+		
+		"MenuButton":{
+			"extends":"Label",
+			"padding":[8,3],
+			"states":{
+				"hover":{
+					"textColor":"accent",
+					"backgroundColor":"brightest"
+				},
+				"active":{
+					"textColor":"accent",
+					"backgroundColor":"brightest"
+				},
+				"selected":{
+					"textColor":"accent",
+					"backgroundColor":"brightest"
+				}
+			}
+		},
+		
+		"MenuBar":{
+			"extends":"ToolBar",
+			"backgroundColor":"darkest",
+			"margin":[ 0 ]
+		},
+		
+		"MenuSeparator":{
+			"padding":[ 0,0,0,1 ],
+			"backgroundColor":"dark",
+			"border":[ 8,8,7,7 ]
+		},
+
+		"TabView":{
+		},
+		
+		"TabBar":{
+			"extends":"ToolBar",
+			"padding":[ 0,0,0,0 ],
+			"backgroundColor":"bright"
+		},
+		
+		"TabButton":{
+            "skinColor":"brighter",
+			"extends":"Button",
+			"font":"small",
+			"padding":[14,12,14,12],
+			"border":[0,0,0,4],
+			"borderColor":"accentDark",
+			"skin":"prime_assets/square.png",
+			"textColor":"text-background",
+			"states":{
+				"hover":{
+					"skinColor":"bright",
+					"textColor":"text-default"
+				},
+				"active":{
+					"textColor":"text-default"
+				},
+				"selected":{
+					"skinColor":"brightest",
+					"textColor":"text-highlight",
+					"borderColor":"accent"
+				}
+			}
+		},
+		
+		"TabClose":{
+			"margin":[32,0,0,0 ],
+			"icons":"prime_assets/tabclose_icons.png",
+			"iconColor":"text-disabled",
+			"states":{
+				"hover":{
+					"iconColor":"accent"
+				},
+				"active":{
+					"iconColor":"accent"
+				}
+			}
+		},
+		
+		"TableView":{
+			"extends":"DockingView"
+		},
+		
+		"TableHeader":{
+			"extends":"Label",
+			"textColor":"text-highlight",
+			"borderColor":"darker"
+		},
+		
+		"TableColumn":{
+		},
+		
+		"TreeView":{
+			"backgroundColor":"dark",
+			"icons":"prime_assets/treeview_icons.png",
+			"iconColor":"#8fff"
+		},
+		
+		"TreeViewContent":{
+			"padding":[3]
+		},
+
+		"TreeViewNode":{
+			"font":"small",
+			"padding":[0,2,0,2],
+			"states":{
+				"hover":{
+					// "backgroundColor":"darker",
+					"textColor":"text-highlight"
+				},
+				"selected":{
+					// "backgroundColor":"darker",
+					"textColor":"text-highlight"
+				}
+			}
+		},
+		
+		"ListView":{
+			"backgroundColor":"dark"
+		},
+		
+		"ListViewContent":{
+			"padding":[2]
+		},
+		
+		"ListViewItem":{
+			"padding":[1],
+			"states":{
+				"hover":{
+					"backgroundColor":"darker"
+				},
+				"selected":{
+					"backgroundColor":"darker",
+					"textColor":"text-highlight"
+				}
+			}
+		},
+		
+		"FileBrowser":{
+			"extends":"TreeView"
+		},
+		
+		"HtmlView":{
+		},
+		
+		"Console":{
+			"backgroundColor":"dark"
+		},
+		
+		"Dialog":{
+			"skin":"prime_assets/dialog_skin.png",
+			"skinColor":"bright"
+		},
+		
+		"DialogTitle":{
+			"extends":"Label",
+			"backgroundColor":"brightest"
+		
+		},
+		
+		"DialogContent":{
+			"padding":[ 8,8,8,4 ]
+		},
+		
+		"DialogActions":{
+			"padding":[ 8,4,8,4 ]
+		}
+	}
+}

+ 9 - 0
assets/themes/theme-prime-blue.json

@@ -0,0 +1,9 @@
+//Theme by @Hezkore
+{
+	"extends":"theme-prime-base",
+	
+	"colors":{
+		"accent": "#3CD6E7",
+		"accentDark": "#0F3E4D"
+	}
+}

+ 9 - 0
assets/themes/theme-prime-red.json

@@ -0,0 +1,9 @@
+//Theme by @Hezkore
+{
+	"extends":"theme-prime-base",
+	
+	"colors":{
+		"accent": "#E74C3C",
+		"accentDark": "#4D1E0F"
+	}
+}

+ 1 - 1
assets/themes/theme-smooth.json

@@ -1,4 +1,4 @@
-//Theme by Hezkore
+//Theme by @Hezkore
 
 {
 	"extends":"ted2-default",

+ 1 - 1
assets/themes/theme-smooth-warm.json → assets/themes/theme-warm.json

@@ -1,4 +1,4 @@
-//Theme by Ethernaut, vaguely based on "smooth" by Hezcore
+//Theme by Ethernaut, vaguely based on "smooth" by Hezkore
 {
 	"extends":"ted2-default",
  

+ 3 - 1
assets/themes/themes.json

@@ -4,6 +4,8 @@
 	"Monkey 1":"theme-monkey1",
 	"Blitzed":"theme-blitzed",
 	"Basic Blue":"theme-Basic-Blue",
+	"Prime - Red":"theme-prime-red",
+	"Prime - Blue":"theme-prime-blue",
 	"Smooth":"theme-smooth",
-	"Smooth warm":"theme-smooth-warm"
+	"Warm":"theme-warm"
 }

+ 31 - 0
dialog/DialogExt.monkey2

@@ -12,21 +12,52 @@ Class DialogExt Extends Dialog
 	End
 	
 	Method Show()
+		
 		If _opened Return
 		_opened = True
 		Open()
 		OnShow()
 	End
 	
+	Method ShowModal:Bool()
+		
+		If _opened Return False
+		
+		_opened = True
+		Open()
+		OnShow()
+		
+		App.BeginModal( Self )
+		_wait=New Future<Bool>
+		Local ok:=_wait.Get()
+		App.EndModal()
+		
+		Return ok
+	End
+	
 	Method Hide()
+	
+		HideWithResult( True )
+	End
+	
+	Method HideWithResult( ok:Bool )
+		
 		If Not _opened Return
+		
 		_opened = False
 		Close()
 		OnHide()
+		
+		If _wait
+			_wait.Set( ok )
+			_wait=Null
+		Endif
 	End
 	
+	
 	Private
 	
 	Field _opened:Bool
+	Field _wait:Future<Bool>
 	
 End

+ 231 - 131
dialog/PrefsDialog.monkey2

@@ -7,81 +7,128 @@ Class PrefsDialog Extends DialogExt
 	
 	Method New()
 		
-		Title="Prefs"
+		Title="Preferences"
 		
-		_acShowAfter=New TextField( ""+Prefs.AcShowAfter )
+		Local tabView:=New TabView
+		Local docker:DockingView
 		
-		_acEnabled=New CheckButton( "Enabled" )
-		_acEnabled.Checked=Prefs.AcEnabled
+		' Main
+		'
+		docker=GetMainDock()
+		tabView.AddTab( "Common",docker,True )
 		
-		_acKeywordsOnly=New CheckButton( "Keywords only" )
-		_acKeywordsOnly.Checked=Prefs.AcKeywordsOnly
+		' Editor
+		'
+		docker=GetEditorDock()
+		tabView.AddTab( "Editor",docker )
 		
-		_acUseTab=New CheckButton( "Choose by Tab" )
-		_acUseTab.Checked=Prefs.AcUseTab
+		' Completion
+		'
+		docker=GetCompletionDock()
+		tabView.AddTab( "AutoComplete",docker )
 		
-		_acUseEnter=New CheckButton( "Choose by Enter" )
-		_acUseEnter.Checked=Prefs.AcUseEnter
+		' Chat
+		'
+		docker=GetChatDock()
+		tabView.AddTab( "IRC chat",docker )
 		
-		_acUseSpace=New CheckButton( "Choose by Space" )
-		_acUseSpace.Checked=Prefs.AcUseSpace
 		
-		_acUseDot=New CheckButton( "Choose by Dot (.)" )
-		_acUseDot.Checked=Prefs.AcUseDot
+		ContentView=tabView
 		
-		_acNewLineByEnter=New CheckButton( "Add new line (by Enter)" )
-		_acNewLineByEnter.Checked=Prefs.AcNewLineByEnter
+		Local apply:=AddAction( "Apply changes" )
+		apply.Triggered=OnApply
 		
-		_editorToolBarVisible=New CheckButton( "ToolBar visible" )
-		_editorToolBarVisible.Checked=Prefs.EditorToolBarVisible
+		_acShowAfter.Activated+=_acShowAfter.MakeKeyView
 		
-		_editorGutterVisible=New CheckButton( "Gutter visible" )
-		_editorGutterVisible.Checked=Prefs.EditorGutterVisible
+		Deactivated+=MainWindow.UpdateKeyView
+	End
+	
+	
+	Private
+	
+	Const _defaultFont:="(default)"
+	Field _acEnabled:CheckButton
+	Field _acUseTab:CheckButton
+	Field _acUseEnter:CheckButton
+	Field _acUseSpace:CheckButton
+	Field _acUseDot:CheckButton
+	Field _acNewLineByEnter:CheckButton
+	Field _acKeywordsOnly:CheckButton
+	Field _acShowAfter:TextField
+	
+	Field _editorToolBarVisible:CheckButton
+	Field _editorGutterVisible:CheckButton
+	Field _editorShowWhiteSpaces:CheckButton
+	Field _editorFontPath:TextField
+	Field _editorFontSize:TextField
+	Field _editorShowEvery10LineNumber:CheckButton
+	Field _editorCodeMapVisible:CheckButton
+	Field _editorAutoIndent:CheckButton
+	
+	Field _mainToolBarVisible:CheckButton
+	Field _mainProjectTabsRight:CheckButton
+	Field _mainProjectIcons:CheckButton
+	
+	Field _monkeyRootPath:TextField
+	
+	Field _chatNick:TextField
+	Field _chatServer:TextField
+	Field _chatPort:TextField
+	Field _chatRooms:TextField
+	
+	Method OnApply()
+	
+		Prefs.AcEnabled=_acEnabled.Checked
+		Prefs.AcUseTab=_acUseTab.Checked
+		Prefs.AcUseEnter=_acUseEnter.Checked
+		Prefs.AcUseSpace=_acUseSpace.Checked
+		Prefs.AcUseDot=_acUseDot.Checked
+		Prefs.AcNewLineByEnter=_acNewLineByEnter.Checked
+		Prefs.AcKeywordsOnly=_acKeywordsOnly.Checked
+		Local count:=Max( 1,Int( _acShowAfter.Text ) )
+		Prefs.AcShowAfter=count
 		
-		_mainToolBarVisible=New CheckButton( "ToolBar visible" )
-		_mainToolBarVisible.Checked=Prefs.MainToolBarVisible
+		Prefs.EditorToolBarVisible=_editorToolBarVisible.Checked
+		Prefs.EditorGutterVisible=_editorGutterVisible.Checked
+		Prefs.EditorShowWhiteSpaces=_editorShowWhiteSpaces.Checked
+		Local path:=_editorFontPath.Text.Trim()
+		If Not path Or path=_defaultFont Then path=""
+		Prefs.EditorFontPath=path
+		Local size:=_editorFontSize.Text.Trim()
+		If Not size Then size="16" 'default
+		Prefs.EditorFontSize=Int(size)
+		Prefs.EditorShowEvery10LineNumber=_editorShowEvery10LineNumber.Checked
+		Prefs.EditorCodeMapVisible=_editorCodeMapVisible.Checked
+		Prefs.EditorAutoIndent=_editorAutoIndent.Checked
 		
-		_mainProjectTabsRight=New CheckButton( "Project tabs on the right side" )
-		_mainProjectTabsRight.Checked=Prefs.MainProjectTabsRight
+		Prefs.MainToolBarVisible=_mainToolBarVisible.Checked
+		Prefs.MainProjectTabsRight=_mainProjectTabsRight.Checked
+		Prefs.MainProjectIcons=_mainProjectIcons.Checked
 		
-		_editorShowWhiteSpaces=New CheckButton( "Whitespaces visible" )
-		_editorShowWhiteSpaces.Checked=Prefs.EditorShowWhiteSpaces
+		Prefs.IrcNickname=_chatNick.Text
+		Prefs.IrcServer=_chatServer.Text
+		Prefs.IrcPort=Int(_chatPort.Text)
+		Prefs.IrcRooms=_chatRooms.Text
 		
-		_editorShowEvery10LineNumber=New CheckButton( "Every 10th line number" )
-		_editorShowEvery10LineNumber.Checked=Prefs.EditorShowEvery10LineNumber
+		App.ThemeChanged()
 		
-		Local path:=Prefs.EditorFontPath
-		If Not path Then path=_defaultFont
-		_editorFontPath=New TextField( path )
-		_editorFontSize=New TextField( ""+Prefs.EditorFontSize )
+		Hide()
+		Apply()
 		
-		Local chooseFont:=New Action( "..." )
-		chooseFont.Triggered+=Lambda()
-			
-			Local initDir:=RealPath( AssetsDir() )
-			
-			Local path:=MainWindow.RequestFile( "Choose Font",initDir,False,"Font files:ttf;Any files:*" )
-			If Not path Return
-			
-			path=RealPath( path )
-			path=path.Replace( initDir,"" )
-			
-			_editorFontPath.Text=path
-		End
-		Local btnChooseFont:=New PushButton( chooseFont )
+		Prefs.SaveLocalState()
+	End
+	
+	Method GetMainDock:DockingView()
 		
-		Local font:=New DockingView
-		font.AddView( New Label( "Font" ),"left" )
-		font.AddView( _editorFontPath,"left" )
-		font.AddView( btnChooseFont,"left" )
-		font.AddView( _editorFontSize,"left","45" )
+		_mainToolBarVisible=New CheckButton( "ToolBar visible" )
+		_mainToolBarVisible.Checked=Prefs.MainToolBarVisible
 		
-		Local after:=New DockingView
-		after.AddView( New Label( "Show after" ),"left" )
-		after.AddView( _acShowAfter,"left" )
+		_mainProjectTabsRight=New CheckButton( "Project tabs on the right side" )
+		_mainProjectTabsRight.Checked=Prefs.MainProjectTabsRight
+		
+		_mainProjectIcons=New CheckButton( "Project file type icons" )
+		_mainProjectIcons.Checked=Prefs.MainProjectIcons
 		
-		' monkey path
-		'
 		_monkeyRootPath=New TextField( Prefs.MonkeyRootPath )
 		_monkeyRootPath.Enabled=False
 		Local chooseMonkeyPath:=New Action( "..." )
@@ -91,7 +138,7 @@ Class PrefsDialog Extends DialogExt
 		
 			Local path:=MainWindow.RequestDir( "Choose Monkey2 root folder",initDir )
 			If Not path Return
-			
+		
 			' check path
 			Local real:=SetupMonkeyRootPath( path,False )
 			If real
@@ -103,36 +150,134 @@ Class PrefsDialog Extends DialogExt
 				' restore current
 				ChangeDir( initDir )
 			Endif
-			
+		
 		End
 		Local btnChooseMonkeyPath:=New PushButton( chooseMonkeyPath )
 		
+		Local docker:=New DockingView
 		Local monkeyPathDock:=New DockingView
 		monkeyPathDock.AddView( New Label( "Monkey2 root folder" ),"left" )
 		monkeyPathDock.AddView( _monkeyRootPath,"left" )
 		monkeyPathDock.AddView( btnChooseMonkeyPath,"left" )
 		
-		'----------------------------
-		' put into the form
-		'----------------------------
-		Local docker:=New DockingView
-		
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( monkeyPathDock,"top" )
 		
-		docker.AddView( New Label( "------ Main:" ),"top" )
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _mainProjectTabsRight,"top" )
+		docker.AddView( _mainProjectIcons,"top" )
 		docker.AddView( _mainToolBarVisible,"top" )
 		docker.AddView( New Label( " " ),"top" )
 		
-		docker.AddView( New Label( "------ Code Editor:" ),"top" )
+		Return docker
+	End
+	
+	Method GetEditorDock:DockingView()
+		
+		_editorToolBarVisible=New CheckButton( "ToolBar visible" )
+		_editorToolBarVisible.Checked=Prefs.EditorToolBarVisible
+		
+		_editorGutterVisible=New CheckButton( "Gutter visible" )
+		_editorGutterVisible.Checked=Prefs.EditorGutterVisible
+		
+		_editorShowWhiteSpaces=New CheckButton( "Whitespaces visible" )
+		_editorShowWhiteSpaces.Checked=Prefs.EditorShowWhiteSpaces
+		
+		_editorShowEvery10LineNumber=New CheckButton( "Every 10th line number" )
+		_editorShowEvery10LineNumber.Checked=Prefs.EditorShowEvery10LineNumber
+		
+		_editorCodeMapVisible=New CheckButton( "CodeMap visible" )
+		_editorCodeMapVisible.Checked=Prefs.EditorCodeMapVisible
+		
+		_editorAutoIndent=New CheckButton( "Auto indentation" )
+		_editorAutoIndent.Checked=Prefs.EditorAutoIndent
+		
+		Local path:=Prefs.EditorFontPath
+		If Not path Then path=_defaultFont
+		_editorFontPath=New TextField( "" )
+		_editorFontPath.TextChanged+=Lambda()
+		
+			Local enabled:=(_editorFontPath.Text<>_defaultFont)
+			_editorFontSize.Enabled=enabled
+		End
+		_editorFontSize=New TextField( ""+Prefs.EditorFontSize )
+		_editorFontPath.Text=path
+		_editorFontPath.ReadOnly=True
+		
+		Local chooseFont:=New Action( "..." )
+		chooseFont.Triggered+=Lambda()
+		
+			Local initDir:=RealPath( AssetsDir() )
+		
+			Local path:=MainWindow.RequestFile( "Choose Font",initDir,False,"Font files:ttf;Any files:*" )
+			If Not path Return
+		
+			path=RealPath( path )
+			path=path.Replace( initDir,"" )
+		
+			_editorFontPath.Text=path
+		End
+		Local btnChooseFont:=New PushButton( chooseFont )
+		
+		Local resetFont:=New Action( "reset" )
+		resetFont.Triggered+=Lambda()
+		
+			_editorFontPath.Text=_defaultFont
+		End
+		Local btnResetFont:=New PushButton( resetFont )
+		
+		Local font:=New DockingView
+		font.AddView( New Label( "Font" ),"left" )
+		font.AddView( _editorFontPath,"left" )
+		font.AddView( _editorFontSize,"left","45" )
+		font.AddView( btnChooseFont,"left" )
+		font.AddView( btnResetFont,"left" )
+		
+		Local docker:=New DockingView
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _editorToolBarVisible,"top" )
 		docker.AddView( _editorGutterVisible,"top" )
 		docker.AddView( _editorShowWhiteSpaces,"top" )
 		docker.AddView( font,"top" )
 		docker.AddView( _editorShowEvery10LineNumber,"top" )
+		docker.AddView( _editorCodeMapVisible,"top" )
+		docker.AddView( _editorAutoIndent,"top" )
 		docker.AddView( New Label( " " ),"top" )
 		
-		docker.AddView( New Label( "------ Completion:" ),"top" )
+		Return docker
+	End
+	
+	Method GetCompletionDock:DockingView()
+		
+		_acShowAfter=New TextField( ""+Prefs.AcShowAfter )
+		
+		Local after:=New DockingView
+		after.AddView( New Label( "Show after" ),"left" )
+		after.AddView( _acShowAfter,"left" )
+		
+		_acEnabled=New CheckButton( "Enabled" )
+		_acEnabled.Checked=Prefs.AcEnabled
+		
+		_acKeywordsOnly=New CheckButton( "Keywords only" )
+		_acKeywordsOnly.Checked=Prefs.AcKeywordsOnly
+		
+		_acUseTab=New CheckButton( "Choose by Tab" )
+		_acUseTab.Checked=Prefs.AcUseTab
+		
+		_acUseEnter=New CheckButton( "Choose by Enter" )
+		_acUseEnter.Checked=Prefs.AcUseEnter
+		
+		_acUseSpace=New CheckButton( "Choose by Space" )
+		_acUseSpace.Checked=Prefs.AcUseSpace
+		
+		_acUseDot=New CheckButton( "Choose by Dot (.)" )
+		_acUseDot.Checked=Prefs.AcUseDot
+		
+		_acNewLineByEnter=New CheckButton( "Add new line (by Enter)" )
+		_acNewLineByEnter.Checked=Prefs.AcNewLineByEnter
+		
+		Local docker:=New DockingView
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _acEnabled,"top" )
 		docker.AddView( after,"top" )
 		docker.AddView( _acUseTab,"top" )
@@ -142,76 +287,31 @@ Class PrefsDialog Extends DialogExt
 		docker.AddView( _acUseDot,"top" )
 		docker.AddView( _acKeywordsOnly,"top" )
 		docker.AddView( New Label( " " ),"top" )
-		'docker.AddView( New Label( "(Restart IDE to see all changes)" ),"top" )
-		'docker.AddView( New Label( " " ),"top" )
-		
-		ContentView=docker
-		
-		Local apply:=AddAction( "Apply" )
-		apply.Triggered=OnApply
-		
-		_acShowAfter.Activated+=_acShowAfter.MakeKeyView
 		
-		Deactivated+=MainWindow.UpdateKeyView
+		Return docker
 	End
 	
-	
-	Private
-	
-	Const _defaultFont:="(default)"
-	Field _acEnabled:CheckButton
-	Field _acUseTab:CheckButton
-	Field _acUseEnter:CheckButton
-	Field _acUseSpace:CheckButton
-	Field _acUseDot:CheckButton
-	Field _acNewLineByEnter:CheckButton
-	Field _acKeywordsOnly:CheckButton
-	Field _acShowAfter:TextField
-	
-	Field _editorToolBarVisible:CheckButton
-	Field _editorGutterVisible:CheckButton
-	Field _editorShowWhiteSpaces:CheckButton
-	Field _editorFontPath:TextField
-	Field _editorFontSize:TextField
-	Field _editorShowEvery10LineNumber:CheckButton
-	
-	Field _mainToolBarVisible:CheckButton
-	Field _mainProjectTabsRight:CheckButton
-	
-	Field _monkeyRootPath:TextField
-	
-	Method OnApply()
-	
-		Prefs.AcEnabled=_acEnabled.Checked
-		Prefs.AcUseTab=_acUseTab.Checked
-		Prefs.AcUseEnter=_acUseEnter.Checked
-		Prefs.AcUseSpace=_acUseSpace.Checked
-		Prefs.AcUseDot=_acUseDot.Checked
-		Prefs.AcNewLineByEnter=_acNewLineByEnter.Checked
-		Prefs.AcKeywordsOnly=_acKeywordsOnly.Checked
-		Local count:=Max( 1,Int( _acShowAfter.Text ) )
-		Prefs.AcShowAfter=count
+	Method GetChatDock:DockingView()
+		
+		Local chatTable:=New TableView( 2,4 )
+		_chatNick=New TextField( Prefs.IrcNickname )
+		_chatServer=New TextField( Prefs.IrcServer )
+		_chatPort=New TextField( ""+Prefs.IrcPort )
+		_chatRooms=New TextField( Prefs.IrcRooms )
+		chatTable[0,0]=New Label( "Nickname" )
+		chatTable[1,0]=_chatNick
+		chatTable[0,1]=New Label( "Server" )
+		chatTable[1,1]=_chatServer
+		chatTable[0,2]=New Label( "Port" )
+		chatTable[1,2]=_chatPort
+		chatTable[0,3]=New Label( "Rooms" )
+		chatTable[1,3]=_chatRooms
 		
-		Prefs.EditorToolBarVisible=_editorToolBarVisible.Checked
-		Prefs.EditorGutterVisible=_editorGutterVisible.Checked
-		Prefs.EditorShowWhiteSpaces=_editorShowWhiteSpaces.Checked
-		Local path:=_editorFontPath.Text.Trim()
-		If Not path Or path=_defaultFont Then path=""
-		Prefs.EditorFontPath=path
-		Local size:=_editorFontSize.Text.Trim()
-		If Not size Then size="16" 'default
-		Prefs.EditorFontSize=Int(size)
-		Prefs.EditorShowEvery10LineNumber=_editorShowEvery10LineNumber.Checked
-		
-		Prefs.MainToolBarVisible=_mainToolBarVisible.Checked
-		Prefs.MainProjectTabsRight=_mainProjectTabsRight.Checked
-		
-		App.ThemeChanged()
-		
-		Hide()
-		Apply()
+		Local docker:=New DockingView
+		docker.AddView( New Label( " " ),"top" )
+		docker.AddView( chatTable,"top" )
 		
-		Prefs.SaveLocalState()
+		Return docker
 	End
 	
 End

+ 187 - 0
dialog/UpdateModulesDialog.monkey2

@@ -0,0 +1,187 @@
+
+Namespace ted2go
+
+
+Class UpdateModulesDialog Extends DialogExt
+	
+	Method New( targets:StringStack,selectedModules:String,configs:String,clean:Bool )
+		
+		_modsNames.Clear()
+		GetModulesNames( _modsNames )
+		
+		Local dock:=New DockingView
+		
+		Local modsDock:=New DockingView
+		modsDock.AddView( New Label( "Modules:" ),"left" )
+		' select all modules
+		Local btn:=New Button( "All" )
+		btn.Clicked+=Lambda()
+			For Local v:=Eachin _modulesViews
+				v.Checked=True
+			Next
+		End
+		modsDock.AddView( btn,"right" )
+		' select none modules
+		btn=New Button( "None" )
+		btn.Clicked+=Lambda()
+			For Local v:=Eachin _modulesViews
+				v.Checked=False
+			Next
+		End
+		modsDock.AddView( New Label( " " ),"right" )
+		modsDock.AddView( btn,"right" )
+		
+		dock.AddView( modsDock,"top" )
+		
+		' table with modules
+		Local selMods:=New StringStack( selectedModules.Split( " " ) )
+		
+		Local cols:=4
+		Local table:=New TableView( cols,1 )
+		table.Rows=(_modsNames.Length/cols)+1
+		Local r:=0,c:=0,i:=0
+		For Local m:=Eachin _modsNames
+		
+			r=i/cols
+			c=i Mod cols
+			i+=1
+		
+			Local chb:=New CheckButton( m )
+			chb.Checked=(Not selectedModules Or selMods.Contains( m ))
+			table[c,r]=chb
+			_modulesViews.Add( chb )
+		Next
+		dock.AddView( table,"top" )
+		dock.AddView( New Label( " " ),"top" )
+		
+		dock.AddView( New Label( "Targets:" ),"top" )
+		Local targetDock:=New DockingView
+		For Local t:=Eachin targets
+			Local chb:=New CheckButton( t )
+			targetDock.AddView( chb,"left" )
+			_targetsViews.Add( chb )
+			chb.Checked=(t="desktop")
+		Next
+		dock.AddView( targetDock,"top" )
+		dock.AddView( New Label( " " ),"top" )
+		
+		dock.AddView( New Label( "Configs:" ),"top" )
+		Local configDock:=New DockingView
+		'
+		_releaseView=New CheckButton( "release" )
+		_releaseView.Checked=configs.Contains( "release" )
+		configDock.AddView( _releaseView,"left" )
+		'
+		_debugView=New CheckButton( "debug" )
+		_debugView.Checked=configs.Contains( "debug" )
+		configDock.AddView( _debugView,"left" )
+		
+		dock.AddView( configDock,"top" )
+		dock.AddView( New Label( " " ),"top" )
+		
+		_cleanView=New CheckButton( "Clean existing data (rebuild)" )
+		_cleanView.Checked=clean
+		_cleanView.Layout="float"
+		dock.AddView( _cleanView,"top" )
+		dock.AddView( New Label( " " ),"top" )
+		
+		Local actUpdate:=New Action( "Update" )
+		actUpdate.Triggered+=Lambda()
+			
+			If Not SelectedModules
+				ShowMessage( "","Please, select at least one module to update." )
+				Return
+			Endif
+			If SelectedTargets.Empty
+				ShowMessage( "","Please, select at least one target." )
+				Return
+			Endif
+			If Not SelectedConfigs
+				ShowMessage( "","Please, select at least one config." )
+				Return
+			Endif
+			
+			HideWithResult( True )
+		End
+		AddAction( actUpdate )
+		
+		Local actCancel:=New Action( "Cancel" )
+		actCancel.Triggered+=Lambda()
+			HideWithResult( False )
+		End
+		AddAction( actCancel )
+		
+		ContentView=dock
+		
+	End
+	
+	Property SelectedModules:String()
+		
+		Local out:=""
+		Local list:=_modulesViews
+		For Local v:=Eachin list
+			
+			If v.Checked Then out+=v.Text+" "
+		Next
+		Return out
+	End
+	
+	Property SelectedTargets:StringStack()
+	
+		Local out:=New StringStack
+		Local list:=_targetsViews
+		For Local v:=Eachin list
+	
+			If v.Checked Then out.Add( v.Text )
+		Next
+		Return out
+	End
+	
+	Property SelectedConfigs:String()
+	
+		Local out:=""
+		If _releaseView.Checked Then out="release"
+		If _debugView.Checked
+			If out Then out+=" "
+			out+="debug"
+		Endif
+		Return out
+	End
+	
+	Property NeedClean:Bool()
+	
+		Return _cleanView.Checked
+	End
+	
+	
+	Private
+	
+	Field _modulesViews:=New Stack<CheckButton>
+	Field _targetsViews:=New Stack<CheckButton>
+	Field _releaseView:CheckButton,_debugView:CheckButton
+	Field _cleanView:CheckButton
+	
+	Global _modsNames:=New StringStack
+	
+	
+	Function GetModulesNames( out:StringStack )
+	
+		Local modsPath:=MainWindow.ModsPath
+	
+		Local dd:=LoadDir( modsPath )
+	
+		For Local d:=Eachin dd
+			If GetFileType( modsPath+d ) = FileType.Directory
+				Local file:=modsPath + d + "/module.json"
+				If GetFileType( file ) = FileType.File
+					out.Add( d )
+				Endif
+			Endif
+		Next
+	End
+	
+	Function ShowMessage( title:String,msg:String,okButton:String="  OK  " )
+		
+		Dialog.Run( title,New Label( msg ),New String[](okButton),0,0 )
+	End
+End

+ 426 - 0
document/BananasDocument.monkey2

@@ -0,0 +1,426 @@
+
+Namespace ted2go
+
+
+Function IsBananasShowcaseAvailable:Bool()
+	
+	Return GetFileType( Prefs.MonkeyRootPath+"bananas/!showcase/all.bananas" )=FileType.File
+End
+
+Class BananasDocument Extends Ted2Document
+
+	Method New( path:String )
+		Super.New( path )
+		
+		Local scrollView:=New ScrollableViewExt
+		scrollView.Style=App.Theme.GetStyle( "BananasView" )
+		_view=scrollView
+		
+		_table=New TableView( 3,1 )
+		_table.Layout="float"
+		_table.Gravity=New Vec2f( 0.5,0 )
+		scrollView.ContentView=_table
+		
+		_filterPanel=New ToolBar
+		scrollView.AddView( _filterPanel,"bottom" )
+		
+		Local lab:=New Label( "Bananas Showcase" )
+		lab.Style=App.Theme.GetStyle( "BananasTitle" )
+		lab.Gravity=New Vec2f( 0.5,0 )
+		lab.Layout="float"
+		scrollView.AddView( lab,"top" )
+		
+		_list=New ListView
+		_list.ItemDoubleClicked+=Lambda( item:ListView.Item )
+			
+			OpenItem( FindItem( item.Text ) )
+		End
+	End
+
+
+	Protected
+	
+	Method OnLoad:Bool() Override
+	
+		Reset()
+		Parse()
+		ShowElements()
+		UpdateFilterPanel()
+		
+		Return True
+	End
+	
+	Method OnCreateBrowser:View() Override
+	
+		Return _list
+	End
+	
+	Method OnCreateView:View() Override
+		
+		Return _view
+	End
+	
+	
+	Private
+	
+	Const EMPTY_TAG:="#"
+	Const MAX_WIDTH:=300
+	Const MAX_HEIGHT:=200
+	Field _list:ListView
+	Field _table:TableView
+	Field _view:View
+	Field _items:=New Stack<Item>
+	Field _views:=New Stack<View>
+	Field _cols:Int,_rows:Int
+	Field _tags:=New StringStack
+	Field _filterTags:=New StringStack
+	Field _filterViews:=New Stack<ToolButtonExt>
+	Field _filterPanel:ToolBar
+	
+	Struct Item
+		
+		Field title:String
+		Field author:String
+		Field descr:String
+		Field previewPath:String
+		Field sourcePath:String
+		Field homepage:String
+		Field modified:String
+		Field version:String
+		Field tags:String
+		
+	End
+	
+	
+	Method Reset()
+		
+		_items.Clear()
+		_views.Clear()
+		_tags.Clear()
+		_filterTags.Clear()
+		_filterViews.Clear()
+	End
+	
+	Method FindItem:Item( title:String )
+		
+		For Local i:=Eachin _items
+			If i.title = title Return i
+		Next
+		Return Null
+	End
+	
+	Method OpenItem( item:Item )
+		
+		If item<>Null Then MainWindow.OpenDocument( item.sourcePath,True )
+	End
+	
+	Method UpdateFilterPanel()
+		
+		_filterPanel.RemoveAllViews()
+		
+		Local action:=New Action( "Reset" )
+		action.Triggered+=Lambda()
+			_filterTags.Clear()
+			For Local v:=Eachin _filterViews
+				v.IsToggled=False
+			Next
+			OnFilterChanged()
+		End
+		Local button:=New ToolButtonExt( action,"Reset filters" )
+		_filterPanel.AddView( button,"left" )
+		
+		For Local tag:=Eachin _tags
+			action=New Action( tag )
+			button=New ToolButtonExt( action )
+			button.ToggleMode=True
+			button.Toggled+=Lambda( toggled:Bool )
+				If toggled Then _filterTags.Add( tag ) Else _filterTags.Remove( tag )
+				OnFilterChanged()
+			End
+			_filterPanel.AddView( button,"left" )
+			_filterViews.Add( button )
+		Next
+		
+	End
+	
+	Method OnFilterChanged()
+		
+		ShowElements()
+	End
+	
+	Method ShowElements()
+		
+		_table.RemoveAllRows()
+		_list.RemoveAllItems()
+		
+		Local count:=GetVisibleCount()
+		
+		Local cols:=_table.Columns
+		Local rows:=count/cols
+		If count Mod cols <> 0 Then rows+=1
+		
+		_table.Rows=rows
+		
+		Local r:=0,c:=0,i:=0
+		For Local k:=0 Until _items.Length
+			
+			If Not IsItemVisible( _items[k] ) Continue
+			
+			r=i/cols
+			c=i Mod cols
+			i+=1
+			_table[c,r]=_views[k]
+			
+			_list.AddItem( _items[k].title )
+		Next
+		
+	End
+	
+	Method GetVisibleCount:Int()
+		
+		If _filterTags.Empty Return _items.Length
+		
+		Local count:=0
+		For Local i:=Eachin _items
+			If IsItemVisible( i ) Then count+=1
+		Next
+		Return count
+	End
+	
+	Method IsItemVisible:Bool( item:Item )
+		
+		If _filterTags.Empty Return True
+		
+		For Local tag:=Eachin _filterTags
+			if item.tags.Contains( tag ) Return True
+		Next
+		Return False
+	End
+	
+	Method Parse()
+		
+		ParseFile()
+		'ParseFolder()
+		
+		_tags.Sort()
+	End
+	
+	Method ParseFolder()
+		
+		Local dir:=ExtractDir( Path )
+		Local arr:=LoadDir( dir )
+		Local files:=New StringStack
+		For Local f:=Eachin arr
+			If GetFileType( dir+f )<>FileType.Directory Continue
+			Local file:=dir+f+"/info.json"
+			If GetFileType( file )<>FileType.File Continue
+			files.Add( file )
+		Next
+		
+		For Local file:=Eachin files
+			Try 
+				Local folder:=ExtractDir( file )
+				
+				Local jsonData:=JsonObject.Load( file ).Data
+				
+				ParseItem( jsonData,folder,folder )
+				
+			Catch ex:Throwable
+				
+			End
+			
+		Next
+		
+	End
+	
+	Method ParseFile()
+	
+		Local json:=JsonObject.Load( Path )
+		
+		Local arr:=json.GetArray( "bananas" )
+		Local bananasFolder:=FixFolder( json.GetString( "bananasFolder" ) )
+		Local previewsFolder:=FixFolder( json.GetString( "previewsFolder" ) )
+		
+		
+		For Local i:=Eachin arr
+			
+			Try 
+				Local jsonData:=i.ToObject()
+				
+				ParseItem( jsonData,bananasFolder,previewsFolder )
+				
+			Catch ex:Throwable
+				
+				Print "catch"
+			End
+			
+		Next
+	
+	End
+	
+	Method ParseItem:Item( jsonData:StringMap<JsonValue>,bananasFolder:String,previewsFolder:String )
+		
+		Local title:=Json_GetString( jsonData,"title","" )
+		Local author:=Json_GetString( jsonData,"author","(unknown)" )
+		Local descr:=Json_GetString( jsonData,"description","" )
+		Local preview:=Json_GetString( jsonData,"preview","" )
+		Local source:=Json_GetString( jsonData,"mainFile","" )
+		Local homepage:=Json_GetString( jsonData,"homepage","" )
+		Local modified:=Json_GetString( jsonData,"modified","" )
+		Local version:=Json_GetString( jsonData,"version","" )
+		Local tags:=ProcessTags( Json_GetString( jsonData,"tags","" ) )
+		
+		'If Not author Then author="(unknown)"
+		If author Then author="by "+author
+		
+		If GetFileType( preview ) <> FileType.File
+			preview=previewsFolder+preview
+		Endif
+		If GetFileType( source ) <> FileType.File
+			source=bananasFolder+source
+		Endif
+		
+		Local i:=New Item
+		i.title=title
+		i.author=author
+		i.descr=descr
+		i.previewPath=preview
+		i.sourcePath=source
+		i.homepage=homepage
+		i.modified=modified
+		i.version=version
+		i.tags=tags
+		
+		_items.Add( i )
+		
+		Local v:=CreateBananaView( i )
+		_views.Add( v )
+		
+		Return i
+	End
+	
+	Method FixFolder:String( folder:String )
+		
+		If GetFileType( folder )<>FileType.Directory Then folder=AppDir()+folder
+		Return folder
+	End
+	
+	Method CreateBananaView:View( item:Item )
+		
+		Local dock:=New DockingView
+		dock.Style=App.Theme.GetStyle( "BananasCard" )
+		
+		' preview
+		Local img:=Image.Load( item.previewPath )
+		AdjustImageScale( img,MAX_WIDTH,MAX_HEIGHT )
+		Local lab:=New Label( " ",img )
+		dock.AddView( lab,"top" )
+		
+		' title and open button
+		Local titleDock:=New DockingView
+		Local btn:=New Button( "Open" )
+		btn.Style=App.Theme.GetStyle( "BananasButton" )
+		btn.Clicked+=Lambda()
+			OpenItem( item )
+		End
+		lab=New Label( item.title)
+		lab.Style=App.Theme.GetStyle( "BananasTitle" )
+		titleDock.ContentView=lab
+		titleDock.AddView( btn,"right" )
+		dock.AddView( titleDock,"top" )
+		' homepage
+		If item.homepage
+			btn=New Button( "Web" )
+			btn.Style=App.Theme.GetStyle( "BananasButton" )
+			btn.Clicked+=Lambda()
+				OpenUrl( item.homepage )
+			End
+			titleDock.AddView( btn,"right" )
+		Endif
+		
+		' description
+		Local tv:=New TextView( item.descr )
+		tv.Style=App.Theme.GetStyle( "BananasDescription" )
+		Local ww:=tv.Style.Font.TextWidth( item.descr )
+		Local lines:=1+Max( 1.0, ww/(MAX_WIDTH-40) )
+		tv.WordWrap=True
+		tv.ReadOnly=True
+		Local hh:=Min( 96.0,lines*tv.Style.Font.Height+20)
+		tv.MinSize=New Vec2i( 0,hh )
+		dock.AddView( tv,"top" )
+		
+		' authors
+		If item.author Then dock.AddView( New Label( item.author ),"top" )
+		
+		' version and modified
+		Local vers:=item.version
+		If vers Then vers="v"+vers
+		If item.modified
+			If vers Then vers+=" at "
+			vers+=item.modified
+		Endif
+		If vers Then dock.AddView( New Label( vers ),"top" )
+		
+		'tags
+		If item.tags Then dock.AddView( New Label( item.tags ),"top" )
+		
+		Return dock
+	End
+	
+	Method AdjustImageScale( img:Image,fitWidth:Float,fitHeight:Float )
+		
+		If Not img Return
+		
+		Local iw:=img.Width
+		Local ih:=img.Height
+		Local kw:=fitWidth/iw
+		'Local kh:=fitHeight/ih
+		'Local k:=Max( kw,kh )
+		Local k:=kw
+		
+		img.Scale=App.Theme.Scale*k
+	End
+	
+	Method ProcessTags:String( tagsStr:String )
+		
+		Local arr:=tagsStr.Split( "," )
+		tagsStr=""
+		For Local i:=0 Until arr.Length
+			
+			Local t:=arr[i].Trim()
+			If Not t Continue
+			
+			t="#"+t
+			tagsStr+=t+" "
+			'
+			If Not( t=EMPTY_TAG Or _tags.Contains( t ) )
+				_tags.Add( t )
+			Endif
+		Next
+		
+		Return tagsStr
+	End
+	
+End
+
+
+Class BananasDocumentType Extends Ted2DocumentType
+
+	Protected
+	
+	Method New()
+		AddPlugin( Self )
+		
+		Extensions=New String[]( ".bananas" )
+	End
+	
+	Method OnCreateDocument:Ted2Document( path:String ) Override
+	
+		Return New BananasDocument( path )
+	End
+	
+	Private
+	
+	Global _instance:=New BananasDocumentType
+	
+End

+ 60 - 27
document/CodeDocument.monkey2

@@ -1,3 +1,4 @@
+
 Namespace ted2go
 
 
@@ -40,7 +41,7 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		'very important to set FileType for init
 		'formatter, highlighter and keywords
-		FileType=doc.FileType
+		FileType=doc.FileExtension
 		FilePath=doc.Path
 		
 		'AutoComplete
@@ -64,6 +65,10 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		UpdatePrefs()
 	End
 	
+	Property Gutter:CodeGutterView()
+		Return _gutter
+	End
+	
 	Property CharsToShowAutoComplete:Int()
 		
 		Return Prefs.AcShowAfter
@@ -73,16 +78,32 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		ShowWhiteSpaces=Prefs.EditorShowWhiteSpaces
 		
-		RemoveView( _gutter )
-		If Prefs.EditorGutterVisible
-			If Not _gutter Then _gutter=New CodeGutterView( _doc )
-			AddView( _gutter,"left" )
+		Local visible:Bool
+		
+		'gutter view
+		visible=Prefs.EditorGutterVisible
+		If visible
+			If Not _gutter
+				_gutter=New CodeGutterView( _doc )
+				AddView( _gutter,"left" )
+			Endif
 		Endif
+		If _gutter Then _gutter.Visible=visible
+		
+		'codemap view
+		visible=Prefs.EditorCodeMapVisible
+		If visible
+			If Not _codeMap
+				_codeMap=New CodeMapView( Self )
+				AddView( _codeMap,"right" )
+			Endif
+		Endif
+		If _codeMap Then _codeMap.Visible=visible
 		
 		_doc.ArrangeElements()
 	End
 	
-	
+
 	Protected
 	
 	Method OnRenderContent( canvas:Canvas ) Override
@@ -90,9 +111,11 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		Local color:=canvas.Color
 		Local xx:=Scroll.x
 		' whole current line
+		Local r:=CursorRect
+		r.Left=xx
+		r.Right=Width
 		canvas.Color=_lineColor
-		canvas.DrawRect( xx,Line*LineHeight-1,Width,LineHeight+3 )
-		
+		canvas.DrawRect( r )
 		
 		If _doc._debugLine<>-1
 			
@@ -264,7 +287,8 @@ Class CodeDocumentView Extends Ted2CodeTextView
 			
 					Local s:=(indent ? text.Slice( 0,indent ) Else "")
 			
-					If Not beforeIndent
+					' auto indentation
+					If Prefs.EditorAutoIndent And Not beforeIndent
 						text=text.Trim().ToLower()
 						If text.StartsWith( "if" )
 							If Not Utils.BatchContains( text,_arrIf,True )
@@ -534,6 +558,12 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 		Endif
 		
+		' text overwrite mode
+		If event.Key=Key.Insert And Not (shift Or ctrl Or alt)
+			
+			MainWindow.OverwriteTextMode=Not MainWindow.OverwriteTextMode
+		Endif
+		
 	End
 	
 	Method ShowJsonDialog()
@@ -608,13 +638,13 @@ Class CodeDocumentView Extends Ted2CodeTextView
 		
 	End
 	
-	
 	Private
 	
 	Field _doc:CodeDocument
 	Field _prevErrorLine:Int
 	Field _lineColor:Color
 	Field _gutter:CodeGutterView
+	Field _codeMap:CodeMapView
 	
 	Method UpdateThemeColors() Override
 		
@@ -644,11 +674,6 @@ Class CodeDocument Extends Ted2Document
 	
 		_doc=New TextDocument
 		
-		_doc.TextChanged+=Lambda()
-			Dirty=True
-			OnTextChanged()
-		End
-		
 		_doc.LinesModified+=Lambda( first:Int,removed:Int,inserted:Int )
 		
 			Local put:=0
@@ -676,11 +701,13 @@ Class CodeDocument Extends Ted2Document
 		
 		' Editor
 		_codeView=New CodeDocumentView( Self )
-		_codeView.LineChanged += Lambda( prev:Int,cur:Int )
-			If AutoComplete.IsOpened Then AutoComplete.Hide()
-		End
 		_codeView.LineChanged += OnLineChanged
 		
+		_doc.TextChanged+=Lambda()
+			Dirty=True
+			OnTextChanged()
+			_codeView.TextChanged()
+		End
 		
 		' bar + editor
 		_content=New DockingView
@@ -777,7 +804,7 @@ Class CodeDocument Extends Ted2Document
 	' not multipurpose method, need to move into plugin
 	Method PrepareForInsert:String( ident:String,text:String,addSpace:Bool,textLine:String,cursorPosInLine:Int,item:CodeItem )
 		
-		If FileType <> ".monkey2" Return ident
+		If FileExtension <> ".monkey2" Return ident
 		
 		If ident <> text And item And item.IsLikeFunc 'not a keyword
 			
@@ -968,7 +995,7 @@ Class CodeDocument Extends Ted2Document
 		Local text:=TextDocument.GetLine( line )
 		Local posInLine:=_codeView.Cursor-TextDocument.StartOfLine( line )
 		
-		Local can:=AutoComplete.CanShow( text,posInLine,FileType )
+		Local can:=AutoComplete.CanShow( text,posInLine,FileExtension )
 		Return can
 		
 	End
@@ -984,7 +1011,7 @@ Class CodeDocument Extends Ted2Document
 			AutoComplete.DisableUsingsFilter=Not AutoComplete.DisableUsingsFilter
 		Endif
 		
-		AutoComplete.Show( ident,Path,FileType,line )
+		AutoComplete.Show( ident,Path,FileExtension,line )
 		
 		If Not AutoComplete.IsOpened Return
 		
@@ -1043,6 +1070,10 @@ Class CodeDocument Extends Ted2Document
 		_codeView.OnKeyEvent( event )
 	End
 	
+	Property CodeView:CodeDocumentView()
+		Return _codeView
+	End
+	
 	Protected
 	
 	Method OnGetTextView:TextView( view:View ) Override
@@ -1050,7 +1081,6 @@ Class CodeDocument Extends Ted2Document
 		Return _codeView
 	End
 	
-	
 	Private
 
 	Field _doc:TextDocument
@@ -1073,7 +1103,6 @@ Class CodeDocument Extends Ted2Document
 	Field _toolBar:ToolBarExt
 	Field _content:DockingView
 	
-	
 	Method GetToolBar:ToolBarExt()
 		
 		If _toolBar Return _toolBar
@@ -1164,7 +1193,7 @@ Class CodeDocument Extends Ted2Document
 	
 	Method OnLoad:Bool() Override
 	
-		_parser=ParsersManager.Get( FileType )
+		_parser=ParsersManager.Get( FileExtension )
 	
 		Local text:=stringio.LoadString( Path )
 		
@@ -1204,15 +1233,19 @@ Class CodeDocument Extends Ted2Document
 			_treeView.SelectByScope( scope )
 			_prevScope = scope
 		Endif
+		
+		If AutoComplete.IsOpened Then AutoComplete.Hide()
 	End
 	
 	Method UpdateCodeTree()
 		
-		_treeView.Fill( FileType,Path )
+		_treeView.Fill( FileExtension,Path )
 	End
 	
 	Method BgParsing( pathOnDisk:String )
 		
+		If MainWindow.IsTerminating Return
+		
 		ResetErrors()
 		
 		Local errors:=_parser.ParseFile( Path,pathOnDisk,False )
@@ -1254,7 +1287,7 @@ Class CodeDocument Extends Ted2Document
 		' -----------------------------------
 		' catch for parsing
 		
-		If FileType <> ".monkey2" Return
+		If FileExtension <> ".monkey2" Return
 
 		
 		If _timer _timer.Cancel()
@@ -1280,7 +1313,7 @@ Class CodeDocument Extends Ted2Document
 				DeleteFile( tmp )
 				
 				_timer.Cancel()
-							
+				
 				_timer=Null
 				_parsing=False
 				

+ 4 - 4
document/Ted2Document.monkey2

@@ -13,7 +13,7 @@ Class Ted2Document
 	Method New( path:String )
 	
 		_path=path
-		_fileType=ExtractExt( _path )
+		_fileExt=ExtractExt( _path )
 				
 		_modTime=GetFileTime( _path )
 	End
@@ -23,9 +23,9 @@ Class Ted2Document
 		Return _path
 	End
 	
-	Property FileType:String()'file extension
+	Property FileExtension:String()'file extension
 		
-		Return _fileType
+		Return _fileExt
 	End
 	
 	Property ModTime:Long()
@@ -157,7 +157,7 @@ Class Ted2Document
 	Field _modTime:Long
 	Field _state:String
 	Field _view:View
-	Field _fileType:String
+	Field _fileExt:String
 	Field _browser:View
 	
 End

+ 1 - 8
eventfilter/Monkey2KeyEventFilter.monkey2

@@ -15,7 +15,7 @@ Class Monkey2KeyEventFilter Extends TextViewKeyEventFilter
 		
 		Local ctrl:=(event.Modifiers & Modifier.Control)
 		Local shift:=(event.Modifiers & Modifier.Shift)
-			
+		
 		Select event.Type
 		Case EventType.KeyDown
 			
@@ -45,13 +45,6 @@ Class Monkey2KeyEventFilter Extends TextViewKeyEventFilter
 						
 					End
 				
-				Case Key.Insert
-					
-					Local alt:=(event.Modifiers & Modifier.Alt)
-					
-					If Not shift And Not ctrl And Not alt
-						MainWindow.OverrideTextMode=Not MainWindow.OverrideTextMode
-					Endif
 			End
 			
 		End

+ 4 - 4
product/ModuleManager.monkey2

@@ -202,9 +202,9 @@ Class ModuleManager Extends Dialog
 			Local dst:=downloadDir+zip
 
 #if __HOSTOS__="macos"
-			Local cmd:="curl -o ~q"+dst+"~q -data-binary ~q"+src+"~q"
+			Local cmd:="curl -s -o ~q"+dst+"~q -data-binary ~q"+src+"~q"
 #else
-			Local cmd:="wget -O ~q"+dst+"~q ~q"+src+"~q"
+			Local cmd:="wget -q -O ~q"+dst+"~q ~q"+src+"~q"
 #endif
 			_progress.Text="Downloading "+zip+"..."
 			
@@ -427,9 +427,9 @@ Class ModuleManager Extends Dialog
 		progress.Open()
 		
 #if __HOSTOS__="macos"
-		Local cmd:="curl -o ~q"+tmp+"~q ~q"+src+"~q"
+		Local cmd:="curl -s -o ~q"+tmp+"~q ~q"+src+"~q"
 #else
-		Local cmd:="wget -O ~q"+tmp+"~q ~q"+src+"~q"
+		Local cmd:="wget -q -O ~q"+tmp+"~q ~q"+src+"~q"
 #endif
 		If Not _console.Run( cmd )
 		

+ 1 - 1
syntax/Keywords.monkey2

@@ -94,7 +94,7 @@ Class KeywordsPlugin Extends PluginDependsOnFileType
 	Method Init()
 	
 		Local value:JsonValue
-		If IsNeedLoadFromFile() Then value=JsonUtils.LoadValue( GetWordsFilePath(),GetMainFileType() )
+		If IsNeedLoadFromFile() Then value=Json_LoadValue( GetWordsFilePath(),GetMainFileType() )
 		Local s := (value<>Null ? value.ToString() Else GetInternal())
 		Local words:=s.Split( ";" )
 		_keywords=New Keywords( words )

+ 0 - 38
utils/JsonUtils.monkey2

@@ -1,38 +0,0 @@
-
-Namespace ted2go
-
-
-Class JsonUtils
-
-	Function LoadValue:JsonValue( filePath:String,key:String )
-		
-		If GetFileType(filePath) <> FileType.File Return Null
-		
-		Local json:=JsonObject.Load( filePath )
-		
-		Return FindValue( json.Data,key )
-	End
-	
-	Function FindValue:JsonValue( data:StringMap<JsonValue>,key:String )
-	
-		key=key.Replace( "\","/" )
-		Local keys:=key.Split( "/" )
-	
-		Local jval:JsonValue
-		For Local k:=0 Until keys.Length
-			jval=data[ keys[k] ]
-			If Not jval Return Null
-			If k=keys.Length-1 Exit
-			If Not jval.IsObject Return Null
-			data=jval.ToObject()
-		Next
-	
-		Return jval
-	End
-	
-	Private 
-	
-	Method New()
-	End
-	
-End

+ 23 - 0
utils/Utils.monkey2

@@ -165,3 +165,26 @@ Class Utils
 	End
 	
 End
+
+
+Function FileExists:Bool( path:String )
+	
+	Return GetFileType( path )=FileType.File
+End
+
+Function DirectoryExists:Bool( path:String )
+	
+	Return GetFileType( path )=FileType.Directory
+End
+
+Function FormatTime:String( millis:Long,format:String="{min} m {sec} s" )
+	
+	millis/=1000
+	Local mins:=millis/60
+	Local secs:=millis Mod 60
+	
+	Local s:=format.Replace( "{min}",""+mins )
+	s=s.Replace( "{sec}",""+secs )
+	
+	Return s
+End

+ 44 - 0
utils/jsonutils.monkey2

@@ -0,0 +1,44 @@
+
+Namespace ted2go
+
+
+Function Json_LoadValue:JsonValue( filePath:String,key:String )
+	
+	If GetFileType(filePath) <> FileType.File Return Null
+	
+	Local json:=JsonObject.Load( filePath )
+	
+	Return Json_FindValue( json.Data,key )
+End
+
+Function Json_FindValue:JsonValue( data:StringMap<JsonValue>,key:String )
+	
+	key=key.Replace( "\","/" )
+	Local keys:=key.Split( "/" )
+	
+	Local jval:JsonValue
+	For Local k:=0 Until keys.Length
+		jval=data[ keys[k] ]
+		If Not jval Return Null
+		If k=keys.Length-1 Exit
+		If Not jval.IsObject Return Null
+		data=jval.ToObject()
+	Next
+	
+	Return jval
+End
+
+Function Json_GetBool:Bool( json:Map<String,JsonValue>,key:String,def:Bool )
+	
+	Return json[key] ? json[key].ToBool() Else def
+End
+
+Function Json_GetString:String( json:Map<String,JsonValue>,key:String,def:String )
+	
+	Return json[key] ? json[key].ToString() Else def
+End
+
+Function Json_GetInt:Int( json:Map<String,JsonValue>,key:String,def:Int )
+	
+	Return json[key] ? Int(json[key].ToNumber()) Else def
+End

+ 214 - 0
view/CodeMapView.monkey2

@@ -0,0 +1,214 @@
+
+Namespace ted2go
+
+
+Class CodeMapView Extends View
+	
+	Const WIDTH:=160
+	Const PAD:=3.0
+	Field scale:Float=0.33
+	
+	Method New( sourceView:CodeTextView )
+	
+		Super.New()
+	
+		Style=GetStyle( "CodeMapView" )
+		
+		_codeView=sourceView
+	
+		_selColor=App.Theme.GetColor( "codemap-selection" )
+		_padding=PAD*App.Theme.Scale.x
+		App.ThemeChanged+=Lambda()
+			_selColor=App.Theme.GetColor( "codemap-selection" )
+			_padding=PAD*App.Theme.Scale.x
+		End
+		
+	End
+	
+	
+	Protected
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Local ww:=WIDTH*App.Theme.Scale.x
+		Local size:=New Vec2i( ww,VisibleHeight )
+		Return size
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+		
+		Local posY0:Float=event.TransformToView(Self).Location.Y
+		Local posY:=Max( Float(0.0),posY0-BubbleHeight*.5 )
+		
+		Select event.Type
+			
+			Case EventType.MouseDown
+				
+				Local top:=_clickedScrollY*(scale-ScrollKoef)
+				Local inside := posY0>=top And posY0<=top+BubbleHeight
+				If Not inside Then ScrollTo( posY )
+				
+				_dragging=True
+				_clickedMouseY=posY0
+				_clickedScrollY=OwnerScrollY
+				
+			Case EventType.MouseMove
+				
+				If _dragging
+					Local dy:=(posY0-_clickedMouseY)
+					Local hh:=Min( VisibleHeight,ContentHeight )
+					Local percent:=dy/(hh-BubbleHeight)
+					Local dy2:=_maxOwnerScroll*percent
+					Local yy:=_clickedScrollY+dy2
+					OwnerScrollY=yy
+				Endif
+				
+			Case EventType.MouseUp
+				
+				_dragging=False
+
+			Case EventType.MouseWheel
+				
+				_codeView.Scroll=_codeView.Scroll+New Vec2i( 0, -RenderStyle.Font.Height*event.Wheel.y*12 )
+				
+		End
+		
+		event.Eat()
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+		
+		Super.OnRender( canvas )
+		
+		OnRenderMap( canvas )
+		
+		' selection overlay
+		Local ww:=Rect.Width
+		Local hh:Float=BubbleHeight
+		
+		Local yy:Float=OwnerScrollY*(scale-ScrollKoef)
+		
+		canvas.Color=_selColor
+		canvas.DrawRect( 0,yy,ww,hh )
+		
+	End
+	
+	
+	Private
+	
+	Field _codeView:CodeTextView
+	Field _maxOwnerScroll:Float=1.0
+	Field _maxSelfScroll:Float=1.0
+	Field _selColor:Color
+	Field _clickedMouseY:Float
+	Field _clickedScrollY:Float
+	Field _dragging:=False
+	Field _padding:Float
+	
+	
+	Property OwnerScrollY:Float()
+	
+		Return _codeView.Scroll.y
+	
+	Setter( value:Float )
+	
+		Local sc:=_codeView.Scroll
+		sc.Y=Int(value)
+		_codeView.Scroll=sc
+	End
+	
+	Property ScrollKoef:Float()
+	
+		Local hh:=_codeView.ContentView.Frame.Height
+		_maxSelfScroll=Max( Float(0.0),hh*scale-VisibleHeight )
+		_maxOwnerScroll=Max( 0.0,hh-VisibleHeight )
+	
+		Return _maxOwnerScroll > 0 ? _maxSelfScroll/_maxOwnerScroll Else 1.0
+	End
+	
+	Property OwnerContentHeight:Float()
+	
+		Return _codeView.ContentView.Frame.Height
+	End
+	
+	Property BubbleHeight:Float()
+	
+		Return VisibleHeight*scale
+	End
+	
+	Property VisibleHeight:Float()
+	
+		Return _codeView.VisibleRect.Height
+	End
+	
+	Property ContentHeight:Float()
+	
+		Return OwnerContentHeight*scale
+	End
+	
+	Method ScrollTo( posY:Float )
+	
+		Local scrl:=_codeView.Scroll
+		Local percent:=posY/(VisibleHeight-BubbleHeight)
+		Local yy:=_maxOwnerScroll*percent
+		scrl.Y=yy
+		_codeView.Scroll=scrl
+	
+	End
+	
+	Method OnRenderMap( canvas:Canvas )
+	
+		Local yy:Float=_codeView.Scroll.y
+	
+		canvas.PushMatrix()
+	
+		canvas.Translate( _padding,-yy*ScrollKoef+_padding )
+		canvas.Scale( scale,scale )
+	
+		Local times:=Int(OwnerContentHeight/VisibleHeight)+1
+		Local sc:=_codeView.Scroll
+		Local sc2:=New Vec2i
+		Local dy:=VisibleHeight+_codeView.LineHeight
+		Local whiteSpaces:=_codeView.ShowWhiteSpaces
+		_codeView.ShowWhiteSpaces=False
+		Local top:=-yy*ScrollKoef
+		For Local k:=0 Until times
+	
+			' check visibility area
+			If top+VisibleHeight*scale < 0
+				top+=dy*scale
+				sc2.Y=sc2.y+dy
+				Continue
+			Endif
+			If top>VisibleHeight
+				Exit
+			Endif
+			top+=dy*scale
+	
+			_codeView.Scroll=sc2
+			CodeTextViewBridge.ProcessRender( _codeView,canvas )
+			sc2.Y=sc2.y+dy
+			
+		Next
+		_codeView.Scroll=sc
+		_codeView.ShowWhiteSpaces=whiteSpaces
+	
+		canvas.PopMatrix()
+	
+	End
+	
+End
+
+
+Private
+
+' get access to protected methods w/o inheritance
+
+Class CodeTextViewBridge Extends CodeTextView Abstract
+
+	Function ProcessRender( item:CodeTextView,canvas:Canvas )
+		
+		item.OnRenderContent( canvas )
+	End
+	
+End

+ 38 - 41
view/CodeTextView.monkey2

@@ -9,6 +9,7 @@ Class CodeTextView Extends TextView
 	Field Highlighter:Highlighter
 	
 	Field LineChanged:Void( prevLine:Int,newLine:Int )
+	Field TextChanged:Void()
 	
 	Method New()
 		Super.New()
@@ -190,6 +191,7 @@ Class CodeTextView Extends TextView
 		_showWhiteSpaces=value
 	End
 	
+	
 	Protected
 	
 	Method OnContentMouseEvent( event:MouseEvent ) Override
@@ -220,7 +222,7 @@ Class CodeTextView Extends TextView
 				Endif
 				
 				' select next char in override mode
-				If Cursor=Anchor And MainWindow.OverrideTextMode
+				If Cursor=Anchor And MainWindow.OverwriteTextMode
 				
 					' don't select new-line-char ~n
 					If Cursor < Text.Length And Text[Cursor]<>10
@@ -407,71 +409,66 @@ Class CodeTextView Extends TextView
 		_whitespacesColor=App.Theme.GetColor( "textview-whitespaces" )
 	End
 	
-	
-	Private
-	
-	Field _line:Int
-	Field _whitespacesColor:Color
-	Field _showWhiteSpaces:Bool
-	Field _font:Font
-	Field _charw:Int
-	Field _charh:Int
-	Field _tabw:Int
-	
-	Method OnCursorMoved()
-		
-		Local line:=Document.FindLine( Cursor )
-		If line <> _line
-			LineChanged( _line,line )
-			_line=line
-		Endif
-				
-		'If Cursor <> Anchor Return
-		'DoFormat( True )
-		
-	End
-	
 	Method OnRenderLine( canvas:Canvas,line:Int ) Override
 	
 		Super.OnRenderLine( canvas,line )
-		
+	
 		' draw whitespaces
 		If Not _showWhiteSpaces Return
-		
+	
 		Local text:=Document.Text
 		Local colors:=Document.Colors
 		Local r:Recti
-		
+	
 		For Local word:=Eachin WordIterator.ForLine( Self,line )
-		
+	
 			If text[word.Index]=9 ' tab
-				
+	
 				canvas.Color=_whitespacesColor
-				
+	
 				Local len:=word.Length
-				
+	
 				r=word.Rect
 				Local x0:=r.Left,y0:=r.Top+1,y1:=y0+r.Height
 				Local ww:=r.Width/len
 				Local xx:=x0+ww
-				
+	
 				Local after:=word.Index+len
 				If after < text.Length And text[after] > 32 Then len-=1
-				
+	
 				For Local i:=0 Until len
 					canvas.DrawLine( xx,y0,xx,y1 )
 					xx+=ww
 				Next
 			Endif
 		Next
-
+	
+	End
+	
+	
+	Private
+	
+	Field _line:Int
+	Field _whitespacesColor:Color
+	Field _showWhiteSpaces:Bool
+	Field _font:Font
+	Field _charw:Int
+	Field _charh:Int
+	Field _tabw:Int
+	
+	
+	Method OnCursorMoved()
+		
+		Local line:=Document.FindLine( Cursor )
+		If line <> _line
+			LineChanged( _line,line )
+			_line=line
+		Endif
+				
+		'If Cursor <> Anchor Return
+		'DoFormat( True )
+		
 	End
 	
-'	Function DrawDottedLineVert ( canvas:Canvas,x:Float,y1:Float,y2:Float )
-'		
-'		For Local y:=y1 To y2 Step 6
-'			canvas.DrawLine( x,y,x,y+1 )
-'		Next
-'	End
 	
 End

+ 6 - 4
view/ConsoleViewExt.monkey2

@@ -214,7 +214,7 @@ Class ConsoleExt Extends TextView
 	
 	Method OnKeyEvent( event:KeyEvent ) Override
 	
-		If CanCopy And (event.Key = Key.C Or event.Key = Key.Insert) And  event.Type = EventType.KeyDown And event.Modifiers & Modifier.Control
+		If CanCopy And (event.Key = Key.C Or event.Key = Key.Insert) And event.Type = EventType.KeyDown And event.Modifiers & Modifier.Control
 		
 			Copy()
 			Return
@@ -288,11 +288,13 @@ Class ConsoleExt Extends TextView
 		If Not _filter Or text.Find( _filter) <> -1
 			Local cur:=Cursor,anc:=Anchor
 			Local sc:=Scroll
+			Local maxScroll:=LineRect(Document.NumLines-1).Bottom-VisibleRect.Height
+			Local atBottom:=Scroll.y>=maxScroll And cur=anc
 			AppendText( text )
-			Local atBottom:=(Scroll.y-sc.y<=LineRect(Document.NumLines-1).Height) And cur=anc
+			SelectText( Text.Length,Text.Length )
 			If Not atBottom
-				Scroll=sc 'restore
 				SelectText( anc,cur )
+				Scroll=sc 'restore
 			Endif
 			
 		Endif
@@ -300,4 +302,4 @@ Class ConsoleExt Extends TextView
 	
 End
 
-#endif
+#Endif

+ 243 - 0
view/FileBrowserExt.monkey2

@@ -0,0 +1,243 @@
+
+Namespace ted2go
+
+
+Class FileBrowserExt Extends TreeView
+
+	Field FileClicked:Void( path:String )
+
+	Field FileRightClicked:Void( path:String )
+
+	Field FileDoubleClicked:Void( path:String )
+	
+	Method New( rootPath:String="." )
+		Style=GetStyle( "FileBrowser" )
+		
+		GetFileTypeIcons()
+	
+		_rootNode=New Node( Null )
+		
+		RootPath=rootPath
+
+		NodeClicked=OnNodeClicked
+		NodeRightClicked=OnNodeRightClicked
+		NodeDoubleClicked=OnNodeDoubleClicked
+		
+		NodeExpanded=OnNodeExpanded
+		NodeCollapsed=OnNodeCollapsed
+		
+		RootNode=_rootNode
+		
+		Update()
+	End
+	
+	#rem monkeydoc Root path of browser.
+	#end
+	Property RootPath:String()
+	
+		Return _rootPath
+	
+	Setter( path:String )
+	
+		_rootPath=path
+		
+		_rootNode._path=path
+		_rootNode.Text=_rootPath
+	End
+	
+	#rem monkeydoc Updates the browser.
+	#end
+	Method Update()
+	
+		UpdateNode( _rootNode,_rootPath,True )
+	End
+	
+	Protected
+	
+	Method OnValidateStyle() Override
+
+		Super.OnValidateStyle()
+			
+		GetFileTypeIcons()
+		
+		_dirIcon=_fileTypeIcons["._dir"]
+		_fileIcon=_fileTypeIcons["._file"]
+	End
+
+	Private
+	
+	Class Node Extends TreeView.Node
+	
+		Method New( parent:Node )
+			Super.New( "",parent )
+		End
+		
+		Private
+		
+		Field _path:String
+	End
+	
+	Field _rootNode:Node
+	Field _rootPath:String
+	
+	Field _dirIcon:Image
+	Field _fileIcon:Image
+	
+	Method OnNodeClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileClicked( node._path )
+	End
+	
+	Method OnNodeRightClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileRightClicked( node._path )
+	End
+	
+	Method OnNodeDoubleClicked( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileDoubleClicked( node._path )
+	End
+	
+	Method OnNodeExpanded( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		UpdateNode( node,node._path,True )
+	End
+	
+	Method OnNodeCollapsed( tnode:TreeView.Node )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		For Local child:=Eachin node.Children
+			child.RemoveAllChildren()
+		Next
+		
+	End
+	
+	Method UpdateNode( node:Node,path:String,recurse:Bool )
+	
+		If Not path.EndsWith( "/" ) path+="/"
+		Local dir:=filesystem.LoadDir( path )
+		
+		Local dirs:=New Stack<String>
+		Local files:=New Stack<String>
+		
+		For Local f:=Eachin dir
+		
+			Local fpath:=path+f
+			
+			Select GetFileType( fpath )
+			Case FileType.Directory
+				dirs.Push( f )
+			Default
+				files.Push( f )
+			End
+		Next
+		
+		dirs.Sort()
+		files.Sort()
+		
+		Local i:=0,children:=node.Children
+		
+		While i<dir.Length
+		
+			Local f:=""
+			If i<dirs.Length f=dirs[i] Else f=files[i-dirs.Length]
+			
+			Local child:Node
+			
+			If i<children.Length
+				child=Cast<Node>( children[i] )
+			Else
+				child=New Node( node )
+			Endif
+			
+			Local fpath:=path+f
+			
+			child.Text=f
+			child._path=fpath
+			
+			Local icon:Image
+			If Prefs.MainProjectIcons Then 'Only load icon if settings say so
+				icon=GetFileTypeIcon( fpath )
+			Endif
+			
+			If i<dirs.Length
+				If Not icon And Prefs.MainProjectIcons icon=_dirIcon
+				child.Icon=icon
+				If child.Expanded Or recurse
+					UpdateNode( child,fpath,child.Expanded )
+				Endif
+			Else
+				If Not icon And Prefs.MainProjectIcons icon=_fileIcon
+				child.Icon=icon
+				child.RemoveAllChildren()
+			Endif
+			
+			i+=1
+		Wend
+		
+		node.RemoveChildren( i )
+		
+	End
+	
+	Function GetFileTypeIcons:StringMap<Image>()
+	
+		If _fileTypeIcons Return _fileTypeIcons
+		
+		_fileTypeIcons=New StringMap<Image>
+		
+		Local dir:="theme::filetype_icons/"
+		
+		Local types:=stringio.LoadString( dir+"filetypes.txt" ).Split( "~n" )
+	
+		For Local type:=Eachin types
+		
+			type=type.Trim()
+			If Not type Continue
+			
+			Local icon:=Image.Load( dir+type )
+			If Not icon Continue
+			
+			icon.Scale=App.Theme.Scale
+			
+			_fileTypeIcons[ "."+StripExt(type) ]=icon
+		Next
+		
+		App.ThemeChanged+=Lambda()
+			For Local image:=Eachin _fileTypeIcons.Values
+				image.Scale=App.Theme.Scale
+			Next
+		End
+		
+		Return _fileTypeIcons
+	End
+	
+	Protected
+	
+	Method GetFileTypeIcon:Image( path:String ) Virtual
+	
+		Local ext:=ExtractExt( path )
+		If Not ext Return Null
+		
+		Return GetFileTypeIcons()[ ext.ToLower() ]
+	End
+	
+	
+	Private
+	
+	Global _fileTypeIcons:StringMap<Image>
+	
+End

+ 1342 - 0
view/IRCView.monkey2

@@ -0,0 +1,1342 @@
+'IRC module for Monkey 2 by @Hezkore
+'https://github.com/Hezkore/IRC-Monkey2
+
+Namespace ted2go
+
+#Import "assets/"
+
+'=MODULE=
+
+'This class is the main class
+'It may contain multiple server connections at once
+Class IRC
+	Field servers:=New List<IRCServer>
+	
+	'When a new message is received
+	Field OnMessage:Void(message:IRCMessage,container:IRCMessageContainer,server:IRCServer)
+	
+	'When the userlist's been updated
+	Field OnUserUpdate:Void(container:IRCMessageContainer,server:IRCServer)
+	
+	'When a new message container is created
+	Field OnNewContainer:Void(container:IRCMessageContainer,server:IRCServer)
+	
+	'When a message container is removed
+	Field OnRemoveContainer:Void(container:IRCMessageContainer,server:IRCServer)
+	
+	'Add and connect to a new IRC server
+	'Nickname is your user name to connect with
+	'Name/Description of server, can by anything you want
+	'Server is IP/address
+	'Port is server... port...
+	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
+		Local nS:=New IRCServer(nickname,name,server,port)
+		nS.parent=Self
+		nS.type=ContainerType.Server
+		servers.AddLast(nS)
+		OnNewContainer(nS,nS)
+		Return nS
+	End
+End
+
+'Message container types
+Enum ContainerType 
+	None=0
+	Server=1	'Is the server message container
+	Room=2		'Is a room message container
+	User=3		'Is a private chat between two users
+End
+
+'This class acts as a single IRC server connection
+'It contains "message containers", such as rooms and private messages from users
+'The server itself is also a message container
+Class IRCServer Extends IRCMessageContainer
+	Field parent:IRC
+	Field serverAddress:String
+	Field serverPort:Int
+	Field sendBuffer:DataBuffer
+	Field receiveBuffer:DataBuffer=New DataBuffer(512)
+	Field fiberSleep:Float=0.26 'Lower value gets internet messages faster but uses more CPU
+	Field updateFiber:Fiber 'Fiber for updating internet mesages
+	Field socket:Socket
+	Field stream:SocketStream
+	Field nickname:String
+	Field realname:String="KoreIRC"
+	Field messageContainers:=New List<IRCMessageContainer>
+	Field skipContainers:=New String[]("chanserv","nickserv","services","*") 'Make sure these are lower case!
+	
+	Field autoJoinRooms:String
+	Field hasAutoJoinedRooms:Bool
+	
+	Field accepted:Bool
+	
+	Property AutoJoinRooms:String()
+		Return autoJoinRooms
+	Setter(str:String)
+		autoJoinRooms=str
+	End
+	
+	Property Connected:Bool()
+		If stream And socket And Not socket.Closed Return True
+		Return False
+	End
+	
+	'Is this a container we should skip?
+	Method SkipContainer:Bool(compare:String,alsoSkip:String="")
+		compare=compare.ToLower()
+		
+		'Skip containers for the server host name (user server name instead)
+		If compare=socket.PeerAddress.Host.ToLower() Return True
+		
+		'Skip one extra thing, like own username
+		If compare=alsoSkip.ToLower() Return True
+		
+		'Skip the defined skip containers
+		For Local s:=Eachin skipContainers
+			If s=compare Then Return True
+		Next
+		
+		'Skip nothing!
+		Return False
+	End
+	
+	Method Disconnect()
+		If socket Then socket.Close()
+		If stream Then stream.Close()
+		
+		For Local mC:=Eachin Self.messageContainers
+			Self.RemoveMessageContainer(mC)
+		Next
+	End
+	
+	Method Remove() Override
+		parent.servers.Remove(Self)
+	End
+	
+	'Connect using existing nickname, server address and port
+	Method Connect()
+		If Connected Then Return
+		
+		TriggerOnMessage("Connecting to server "+Self.serverAddress+":"+Self.serverPort,Self.name,Self.name,null,Self)
+		
+		socket=Socket.Connect(Self.serverAddress,Self.serverPort)
+		If Not socket
+			TriggerOnMessage("Couldn't connect to server",Self.name,Self.name,null,Self)
+			Return
+		Endif
+		
+		'No delay pretty please
+		socket.SetOption("TCP_NODELAY",True)
+		
+		'Prepare stream
+		stream=New SocketStream(socket)
+		If Not stream
+			TriggerOnMessage("Couldn't create socket stream",Self.name,Self.name,null,Self)
+			Return
+		Endif
+		
+		'Start our update loop in the background
+		UpdateLoop()
+	End
+	
+	'New IRC server
+	Method New(nickname:String,name:String,server:String,port:Int,autoConnect:Bool=True)
+		Self.nickname=nickname
+		Self.name=name
+		Self.serverAddress=server
+		Self.serverPort=port
+		
+		If autoConnect Then Self.Connect()
+	End
+	
+	'Background update loop for internet messages
+	Method UpdateLoop()
+		updateFiber=New Fiber(Lambda()
+			
+			'Send register stuff at start
+			'PASSWORD HERE
+			SendString("USER"+" "+nickname+" "+socket.Address+" "+socket.PeerAddress+" :"+realname)
+			SendString("NICK"+" "+nickname)
+			
+			'Stuff we'll use later
+			Local lineSplit:String[]
+			Local recLine:String
+			Local recLength:Int
+			Local linePos:Int
+			Local line:String
+			Local data:String
+			Local msg:String
+			Local param:String[]
+			
+			'Never ending loop of internet data
+			While Not stream.Eof And Self.Connected
+				
+				'Try to auto join channels
+				If Not hasAutoJoinedRooms And accepted Then
+					hasAutoJoinedRooms=True
+					Local rooms:=autoJoinRooms.Split("#")
+					
+					For Local s:String=Eachin rooms
+						If s.Length>0 Then
+							SendString("JOIN #"+s)
+							Fiber.Sleep(fiberSleep)
+						Endif
+					Next
+					
+				Endif
+				
+				'Do we have anything to receive?
+				While socket.CanReceive
+					'How much should we receive, no more than our buffer length
+					recLength=Min(socket.CanReceive,receiveBuffer.Length)
+					socket.Receive(receiveBuffer.Data,recLength)
+					
+					'Add to our receive string until we've got a line ready
+					recLine+=receiveBuffer.PeekString(0,recLength).Replace("~r","")
+					While recLine.Contains("~n") 'We got a line!
+						'Remove processed line from receive string
+						linePos=recLine.Find("~n")
+						line=recLine.Left(linePos)
+						recLine=recLine.Slice(linePos+1)
+						
+						'Process our newly received line
+						
+						'Remove start : if it exists
+						If line.StartsWith(":") Then line=line.Slice(1)
+						
+						'Return any PING message instantly
+						If line.ToLower().StartsWith("ping ") Then
+							SendString("PONG "+line.Split(" ")[1])
+							Continue 'We're done with the ping message now
+						Endif
+						
+						'If we've got a : somewhere, it's probably a message
+						If line.Contains(":") Then
+							data=line.Split(":")[0]
+							msg=line.Split(data)[1]
+							If msg.StartsWith(":") Then msg=msg.Slice(1)
+						Else
+							data=line
+							msg=Null
+						Endif
+						
+						'Debug print message
+						'Print "MSG: "+data+">"+msg
+						
+						'Process the message we got
+						If data.Contains(" ") Then
+							param=data.Split(" ")
+							OnMessage(msg,param)
+						Endif
+						
+					Wend
+				Wend
+				
+				'Sleep if there's nothing more to receive
+				Fiber.Sleep(fiberSleep)
+			Wend
+			
+			TriggerOnMessage("Disconnected",Self.name,Self.name,null,Self)
+			If stream Then stream.Close()
+		End)
+	End
+	
+	Private
+		'A safe way to read from an array
+		Method GetParam:String(index:Int,param:String[])
+			If param.Length>index Then Return param[index]
+			Return Null
+		End
+		
+		'Internal message processing
+		Method OnMessage(msg:String,param:String[])
+			Local triggerOnMessage:Bool=False
+			Local container:IRCMessageContainer
+			Local fromUser:String
+			Local type:String
+			Local toUser:String
+			
+			fromUser=GetParam(0,param)
+			type=GetParam(1,param)
+			toUser=GetParam(2,param)
+			container=Self
+			
+			While msg.StartsWith(" ")
+				msg=msg.Slice(1)
+			Wend
+			
+			'For message information, visit: https://tools.ietf.org/html/rfc2812
+			
+			'Process message type
+			Select type.ToUpper()
+				Case "001" 'Welcome message
+					'Check if our nickname was accepted or not
+					If Self.nickname<>GetParam(2,param) Then
+						Self.nickname=GetParam(2,param)
+						parent.OnUserUpdate(container,Self)
+					Endif
+					
+				Case "376" 'End of MOTD
+					accepted=True
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					
+				Case "353" 'Username list
+					Local nameSplit:String[]
+					If msg.Contains(" ") Then 
+						nameSplit=msg.Split(" ")
+					Else
+						nameSplit=New String[1]
+						nameSplit[0]=msg
+					Endif
+					
+					container=GetMessageContainer(GetParam(4,param))
+					
+					'Is this the first time we're getting users?
+					If container then
+						If container.gotUsers Then
+							TriggerOnMessage(msg,fromUser,toUser,type,Self)
+						Else
+							container.gotUsers=True
+						Endif
+						
+						container.users.Clear()
+						
+						For Local i:Int=0 Until nameSplit.Length
+							Local nU:=New IRCUser
+							nU.parent=container
+							nU.name=nameSplit[i]
+							container.users.AddLast(nU)
+						Next
+						
+						container.SortUsers()
+					Else
+						TriggerOnMessage(msg,fromUser,toUser,type,Self)
+					Endif
+					
+				Case "366" 'End of username list
+					container=GetMessageContainer(GetParam(3,param))
+					parent.OnUserUpdate(container,Self)
+					
+				Case "331" 'Channel topic empty
+					container=GetMessageContainer(GetParam(3,param))
+					container.topic=""
+					
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					
+				Case "332" 'Channel topic
+					container=GetMessageContainer(GetParam(3,param))
+					container.topic=msg
+					'container.AddMessage("Topic for "+container.name+" is: "+msg,container.name,container.name,type,GetHostname(toUser))
+					
+					'parent.OnMessage(container.messages.Last,container,Self)
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					
+				Case "JOIN" 'Joining a new channel
+					Local chan:String=toUser
+					If Not chan Then chan=msg
+					container=GetMessageContainer(chan,ContainerType.Room)
+					
+					'Update message containers to contain new user
+					Local nU:=New IRCUser
+					nU.parent=container
+					nU.name=GetNickname(fromUser)
+					container.users.AddLast(nU)
+					parent.OnUserUpdate(container,Self)
+					
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					
+				Case "NOTICE"
+					'Notices are sent to all containers!
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					For container=Eachin Self.messageContainers
+						'If container.type=ContainerType.User Then Continue
+						TriggerOnMessage(msg,fromUser,toUser,type,container)
+					Next
+					
+				Case "PRIVMSG"
+					'Check if private messager from user or room
+					If toUser.StartsWith("#") Then
+						'Room
+						container=GetMessageContainer(toUser,ContainerType.Room)
+					Else
+						'Possibly from user!
+						If Not SkipContainer(GetNickname(fromUser)) Then 'skip some users
+							container=GetMessageContainer(GetNickname(fromUser),ContainerType.User)
+							If container.topic<>fromUser Then container.topic=fromUser
+						Endif
+					Endif
+					
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+					
+				Case "PART","QUIT" 'Leaving a channel
+					container=GetMessageContainer(toUser,ContainerType.Room)
+					
+					If GetNickname(fromUser)=nickname Then
+						RemoveMessageContainer(container)
+						container=Self
+					Else
+						For Local u:=Eachin container.users
+							If u.name=GetNickname(fromUser) Then
+								container.users.Remove(u)
+								TriggerOnMessage(msg,fromUser,toUser,type,container)
+								parent.OnUserUpdate(container,Self)
+								Exit
+							Endif
+						Next
+					Endif
+					
+				Case "NICK" 'Changing names
+					'Update local name
+					Local wasSelf:Bool
+					If GetNickname(fromUser)=nickname Then
+						wasSelf=true
+						nickname=msg
+					Endif
+					
+					'Update message containers to new name
+					For container=Eachin Self.messageContainers
+						For Local u:=Eachin container.users
+							If u.name=GetNickname(fromUser) Then
+								u.name=msg
+								container.SortUsers()
+								TriggerOnMessage(msg,fromUser,msg,type,container)
+								parent.OnUserUpdate(container,Self)
+								Exit
+							Endif
+						Next
+					Next
+					
+					'Update server (No one in server but you!)
+					If wasSelf Then
+						container=Self
+						TriggerOnMessage(msg,fromUser,msg,type,container)
+						parent.OnUserUpdate(container,Self)
+					Endif
+					
+				Default
+					TriggerOnMessage(msg,fromUser,toUser,type,container)
+			End
+			
+		End
+		
+		'Internal message triggering method (sends to end user)
+		Method TriggerOnMessage(msg:String,fromUser:String,toUser:String,type:String,container:IRCMessageContainer)
+			If Not container Then container=Self
+			Local m:=New IRCMessage
+			m.text=msg
+			m.fromUser=GetNickname(fromUser)
+			m.toUser=toUser
+			m.type=type
+			m.hostname=fromUser
+			If parent Then parent.OnMessage(m,container,Self)
+		End
+	Public
+	
+	'Send string to server
+	Method SendString(str:String)
+		If Not str.EndsWith("~n") Then str+="~n"
+		Local sendBuffer:DataBuffer=New DataBuffer(str.Length)
+		sendBuffer.PokeString(0,str)
+		socket.Send(sendBuffer.Data,sendBuffer.Length)
+	End
+	
+	'Return a message container
+	'Option to create it if it doesn't exist
+	Method GetMessageContainer:IRCMessageContainer(name:String,addType:ContainerType=ContainerType.None)
+		If name="*" Or name=Self.name Or name.Length<=0 Then Return Self
+		
+		'First we look for existing containers
+		For Local c:=Eachin messageContainers
+			If c.name.ToLower()=name.ToLower() Then Return c
+		Next
+		
+		'Create a new container
+		If addType<>ContainerType.None Then Return AddMessageContainer(name,addType)
+		
+		Return Self
+	End
+	
+	'Add a new message container
+	Method AddMessageContainer:IRCMessageContainer(name:String,type:ContainerType)
+		Local nC:=New IRCMessageContainer
+		nC.parent=Self
+		nC.name=name
+		nC.type=type
+		messageContainers.AddLast(nC)
+		parent.OnNewContainer(nC,Self)
+		Return nC
+	End
+	
+	'Remove a specific message container
+	Method RemoveMessageContainer(container:IRCMessageContainer)
+		messageContainers.Remove(container)
+		If parent Then parent.OnRemoveContainer(container,Self)
+	End
+	
+	'Remove message container by name
+	Method RemoveMessageContainer(name:String)
+		For Local c:=Eachin messageContainers
+			If c.name.ToLower()=name.ToLower() Then
+					RemoveMessageContainer(c)
+				Return
+			Endif
+		Next
+	End
+	
+	'Strips hostname and returns nickname
+	Method GetNickname:String(str:String)
+		If str.Contains("!") Then Return str.Split("!")[0]
+		Return str
+	End
+	
+	'Strips nickname and return hostname
+	Method GetHostname:String(str:String)
+		If str.Contains("!") Then Return str.Split("!")[1]
+		Return str
+	End
+End
+
+'This class is used by servers to contain messages in rooms and private chats
+'It can also contain users and a topic which is mostly used for rooms
+Class IRCMessageContainer
+	Field parent:IRCServer
+	Field name:String
+	Field topic:String
+	Field type:ContainerType
+	Field users:=New List<IRCUser>
+	Field gotUsers:Bool 'Have we gotten users before?
+	Field messages:=New List<IRCMessage>
+	
+	Method Remove() Virtual
+		parent.messageContainers.Remove(Self)
+	End
+	
+	Method GetUser:IRCUser(nick:String)
+		nick=nick.ToLower()
+		For Local u:=Eachin Self.users
+			If u.name.ToLower()=nick Then Return u
+		Next
+		Return Null
+	End
+	
+	'Add a message to this container
+	Method AddMessage(msg:String,fromUser:String="",toUser:String="",type:String="",hostname:string="")
+		Local nM:=New IRCMessage
+		nM.parent=Self
+		nM.text=msg
+		nM.fromUser=fromUser
+		nM.toUser=toUser
+		nM.type=type
+		nM.hostname=hostname
+		messages.AddLast(nM)
+	End
+	
+	'Sort the userlist
+	Method SortUsers()
+		Self.users.Sort(Lambda:Int(x:IRCUser,y:IRCUser)
+			Return CompareNames(x.name,y.name)
+		End)
+	End
+	
+	'Compare usernnames (for sorting)
+	Function CompareNames:Int(lhs:String,rhs:String)
+		if lhs.StartsWith("@") and rhs.StartsWith( "@" ) return lhs<=>rhs
+		if lhs.StartsWith("@") return -1
+		if rhs.StartsWith("@") return +1
+		if lhs.StartsWith("+") and rhs.StartsWith( "+" ) return lhs<=>rhs
+		if lhs.StartsWith("+") return -1
+		if rhs.StartsWith("+") return +1
+		Return lhs<=>rhs
+	End
+End
+
+'A single chat message
+'Used by message containers
+Class IRCMessage
+	Field parent:IRCMessageContainer
+	Field text:String
+	Field fromUser:String
+	Field toUser:String
+	Field type:String
+	Field hostname:String
+	Field time:String
+	
+	Function CurrentTime:String()
+		Local timePtr:=std.time.Time.Now()
+		Return timePtr.Hours+":"+timePtr.Minutes
+	End
+	
+	Method New()
+		Self.time=CurrentTime()
+	End
+End
+
+'A single user
+'Used by message containers
+Class IRCUser
+	Field parent:IRCMessageContainer
+	Field name:String
+End
+
+'=IRC VIEW=
+
+'This is a pre-made IRC client, ready to be used in any MojoX application
+Class IRCView Extends DockingView
+	Field ircHandler:IRC
+	
+	Field introScreen:IRCIntroView
+	Field chatScreen:DockingView
+	
+	Field topicField:TextField
+	Field historyField:TextView
+	Field bottomDocker:DockingView
+	Field inputField:TextField
+	Field nicknameLabel:Label
+	Field userList:ListView
+	Field serverTree:TreeView
+	Field selectedTreeNode:TreeView.Node
+	Field selectedServer:IRCServer
+	Field selectedMessageContainer:IRCMessageContainer
+	
+	Property Intro:IRCIntroView()
+		Return introScreen
+	End
+	
+	Method New()
+		Super.New()
+		
+		introScreen=New IRCIntroView(Self)
+		chatScreen=New DockingView
+		
+		'We move to chat screen by default and intro screen if it's used!
+		Self.ContentView=chatScreen
+		
+		'LAYOUT
+		
+		'Chat history field
+		historyField=New TextView
+		historyField.ReadOnly=True
+		historyField.WordWrap=True
+		chatScreen.ContentView=historyField
+		
+		'User list
+		userList=New ListView
+		chatScreen.AddView(userList,"right",Style.Font.TextWidth("arandomusername!"),True)
+		userList.ItemDoubleClicked+=Lambda(item:ListView.Item)
+			If selectedServer And selectedServer.accepted Then
+				selectedMessageContainer=selectedServer.AddMessageContainer(item.Text,ContainerType.User)
+				If selectedMessageContainer Then
+					UpdateServerTree()
+					serverTree.NodeClicked(GetMessageContainerNode(item.Text,selectedServer.name))
+				Endif
+			Endif
+		End
+		
+		'Server tree list
+		serverTree=New TreeView
+		chatScreen.AddView(serverTree,"left",Style.Font.TextWidth("irc.randomserver.com"),True)
+		
+		serverTree.NodeRightClicked+=Lambda(node:TreeView.Node)
+			serverTree.NodeClicked(node) 'Select node we right clicked
+			If Not selectedServer Or Not selectedMessageContainer Then Return
+			
+			Local menu:=New Menu
+			
+			'Is this a root node?
+			If node=serverTree.RootNode Then
+				'Nothing special
+			Else
+				'Is this is server node?
+				If selectedServer=selectedMessageContainer Then
+					
+					menu.AddAction("Add Channel").Triggered=Lambda()
+						New Fiber(Lambda()
+							Local chan:=RequestString("Enter channel name","Add new channel")
+							If chan Then
+								While chan.StartsWith("#") Or chan.StartsWith(" ")
+									chan=chan.Slice(1)
+								Wend
+								selectedServer.SendString("JOIN #"+chan)
+							Endif
+						End)
+					End
+					
+					menu.AddAction("Rename").Triggered=Lambda()
+						New Fiber(Lambda()
+							Local name:=RequestString("Enter new name","Change server name")
+							If name Then
+								'Does this name already exist?
+								Local exists:Bool
+								For Local s:=Eachin ircHandler.servers
+									If name.ToLower()=s.name.ToLower() Then
+										Notify("Name already exists","This name is already in use")
+										exists=True
+										Exit
+									Endif
+								Next
+								
+								If Not exists Then 
+									selectedServer.name=name
+									node.Text=name
+								Endif
+							Endif
+						End)
+					End
+					
+					If selectedServer.Connected Then
+						menu.AddAction("Disconnect").Triggered=Lambda()
+							selectedServer.Disconnect()
+						End
+					Else
+						menu.AddAction("Connect").Triggered=Lambda()
+							selectedServer.Connect()
+						End
+						
+						menu.AddAction("Remove").Triggered=Lambda()
+							selectedServer.Remove()
+							selectedMessageContainer=Null
+							selectedServer=Null
+							UpdateHistory()
+							node.Remove()
+							UpdateServerTree()
+						End
+					Endif
+					
+				Else
+					'Public room
+					If selectedMessageContainer.type=ContainerType.Room
+						menu.AddAction("Leave").Triggered=Lambda()
+							selectedServer.SendString("PART "+selectedMessageContainer.name)
+						End
+					Else 'Private room
+						menu.AddAction("Close").Triggered=Lambda()
+							selectedMessageContainer.Remove()
+							selectedMessageContainer=selectedServer
+							UpdateHistory()
+							node.Remove()
+						End
+					Endif
+				Endif
+			Endif
+			
+			'Always have the option to add a new server
+			menu.AddAction("Add Server").Triggered=Lambda()
+				New Fiber(Lambda()
+					Local serv:=RequestString("Server address","Add new server")
+					Local port:Int
+					Local nick:String
+					If serv Then
+						port=RequestInt("Server port","Add new server",6667)
+						If port Then
+							nick=RequestString("Nickname","Add new server")
+							If nick Then ircHandler.AddServer(nick,serv,serv,port)
+						Endif
+					Endif
+				End)
+			End
+			
+			menu.Open( )
+		End
+		
+		serverTree.NodeClicked+=Lambda(node:TreeView.Node)
+			If Not node Then Return
+			selectedTreeNode=node
+			
+			'Deselect everything except clicked node!
+			For Local s:=Eachin serverTree.RootNode.Children
+				s.Selected=False
+				For Local c:=Eachin s.Children 'Deselect kids too
+					c.Selected=False
+				Next
+			Next
+			
+			'Select clicked node
+			node.Selected=True
+			node.Icon=Null
+			
+			'Find the root server
+			If node.Parent=serverTree.RootNode Then
+				For Local s:=Eachin ircHandler.servers
+					If s.name=node.Text Then
+						selectedServer=s
+						Exit
+					Endif
+				Next
+			Endif
+			
+			'Find the actual message container we've clicked (can return the server itself!)
+			If selectedServer Then
+				selectedMessageContainer=selectedServer.GetMessageContainer(node.Text)
+			Endif
+			
+			UpdateHistory()
+			UpdateUsers()
+		End
+		
+		'Topic field
+		topicField=New TextField()
+		topicField.BlockCursor=False
+		topicField.CursorType=CursorType.Line
+		topicField.ReadOnly=True
+		chatScreen.AddView(topicField,"top")
+		
+		'Bottom stuff
+		bottomDocker=New DockingView
+		chatScreen.AddView(bottomDocker,"bottom")
+		
+		'Nickname label
+		nicknameLabel=New Label()
+		bottomDocker.AddView(nicknameLabel,"left")
+		nicknameLabel.Clicked=Lambda()
+			If selectedServer And selectedServer.accepted Then
+				New Fiber(Lambda()
+					Local newNick:String=RequestString("Enter new nickname","Change nickname")
+					newNick=newNick.Trim()
+					If selectedServer.nickname<>newNick Then selectedServer.SendString("NICK "+newNick)
+				End)
+			Endif
+		End
+		
+		'User input field
+		inputField=New TextField
+		inputField.BlockCursor=False
+		inputField.CursorType=CursorType.Line
+		inputField.CursorBlinkRate=2.5
+		inputField.Entered+=Lambda()
+			SendInput(inputField.Text)
+			inputField.Text=Null
+		End
+		App.KeyEventFilter+=Lambda( event:KeyEvent )
+			If App.KeyView=inputField And event.Key=Key.KeypadEnter
+				SendInput(inputField.Text)
+				inputField.Text=Null
+			Endif
+		End
+		
+		
+		bottomDocker.ContentView=inputField
+		
+		'Setup IRC
+		ircHandler=New IRC
+		ircHandler.OnMessage=OnMessageIRC
+		ircHandler.OnUserUpdate=OnUserUpdateIRC
+		ircHandler.OnNewContainer=OnNewContainerIRC
+		ircHandler.OnRemoveContainer=OnRemoveContainerIRC
+	End
+	
+	'Add and connect to a new IRC server
+	'Nickname is your user name to connect with
+	'Name/Description of server, can by anything you want
+	'Server is IP/address
+	'Port is server... port...
+	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
+		Local nS:IRCServer
+		
+		If ircHandler Then
+			For Local s:=Eachin ircHandler.servers
+				If name.ToLower()=s.name.ToLower() Then
+					Notify("Name already exists","A server with this name already exists")
+					Return Null
+				Endif
+			Next
+			
+			nS=ircHandler.AddServer(nickname,name,server,port)
+		Endif
+		
+		Return nS
+	End
+	
+	Method SendInput(text:String)
+		If Not selectedMessageContainer Then Return
+		
+		text=text.Trim().Replace("~n","").Replace("~r","").Replace("~t","")
+		If text.Length<=0 Then Return
+		
+		'Make this a PRIVMSG automatically?
+		If text.StartsWith("/") Then
+			'Nope, send raw
+			text=text.Right(text.Length-1)
+			selectedMessageContainer.AddMessage(selectedServer.nickname+": "+text)
+		Else
+			'Yep, add PRIVMSG and send!
+			selectedMessageContainer.AddMessage(selectedServer.nickname+": "+text)
+			text="PRIVMSG "+selectedMessageContainer.name+" :"+text
+		Endif
+		
+		ircHandler.servers.First.SendString(text)
+		AddChatMessage(selectedMessageContainer.messages.Last)
+	End
+	
+	Method OnMessageIRC(message:IRCMessage,container:IRCMessageContainer,server:IRCServer)
+		'For message information, visit: https://tools.ietf.org/html/rfc2812
+		Local doNotify:Bool 'Should we notify the user?
+		
+		Select message.type
+			Case "332" 'TOPIC
+				container.AddMessage("Topic for "+container.name+" is: "+message.text)
+				
+			Case "JOIN"
+				If message.fromUser=server.nickname Then
+					container.AddMessage("You are now talking in "+container.name)
+				Else
+					container.AddMessage(message.fromUser+" joined "+container.name)
+				Endif
+				
+			Case "PART"
+				If message.text Then
+					container.AddMessage(message.fromUser+" left "+container.name+" (reason: "+message.text+")")
+				Else
+					container.AddMessage(message.fromUser+" left "+container.name)
+				Endif
+				
+			Case "QUIT"
+				If message.text Then
+					container.AddMessage(message.fromUser+" quit (reason: "+message.text+")")
+				Else
+					container.AddMessage(message.fromUser+" quit")
+				Endif
+				
+			Case "NICK"
+				If nicknameLabel.Text.Left(nicknameLabel.Text.Length-1) Then
+					container.AddMessage("You are now known as "+message.toUser)
+				Else
+					container.AddMessage(message.fromUser+" is now known as "+message.toUser)
+				Endif
+				
+			Case "PRIVMSG"
+				container.AddMessage(message.fromUser+": "+message.text)
+				doNotify=True
+			
+			Case "NOTICE"
+				container.AddMessage("NOTICE >"+message.fromUser+"<: "+message.text)
+			
+			Default
+				container.AddMessage(message.fromUser+": "+message.text)
+				doNotify=True
+		End
+		
+		'Display message if we're in that container right now
+		If container=selectedMessageContainer Then
+			AddChatMessage(container.messages.Last)
+		Elseif doNotify Then 'Notify user perhaps?
+			Local node:TreeView.Node=GetMessageContainerNode(container.name,server.name)
+			If node Then node.Icon=App.Theme.OpenImage("irc/notice.png")
+		Endif
+	End
+	
+	Method OnUserUpdateIRC(container:IRCMessageContainer,server:IRCServer)
+		If container=selectedMessageContainer Then UpdateUsers()
+	End
+	
+	Method OnNewContainerIRC(container:IRCMessageContainer,server:IRCServer)
+		UpdateServerTree()
+		If container.type<>ContainerType.User Then
+			SelectMessageContainer(container.name,server.name)
+		Endif
+	End
+	
+	Method OnRemoveContainerIRC(container:IRCMessageContainer,server:IRCServer)
+		UpdateServerTree()
+		If container=selectedMessageContainer Then
+			selectedMessageContainer=Null
+			If container=server Then 
+				selectedServer=Null
+			Else
+				SelectMessageContainer(server.name)
+			Endif
+		Endif
+	End
+	
+	Method GetMessageContainerNode:TreeView.Node(serverName:String)
+		serverName=serverName.ToLower()
+		
+		For Local s:=Eachin serverTree.RootNode.Children
+			If s.Text.ToLower()=serverName Then Return s
+		Next
+		
+		Return Null
+	End
+	
+	Method GetMessageContainerNode:TreeView.Node(containerName:String,serverName:String)
+		serverName=serverName.ToLower()
+		containerName=containerName.ToLower()
+		
+		For Local s:=Eachin serverTree.RootNode.Children
+			If s.Text.ToLower()=serverName Then
+				For Local c:=Eachin s.Children
+					If c.Text.ToLower()=containerName Then Return c
+				Next
+			Endif
+		Next
+		
+		Return Null
+	End
+	
+	Method SelectMessageContainer(serverName:String)
+		Local foundNode:=GetMessageContainerNode(serverName)
+		If foundNode Then serverTree.NodeClicked(foundNode)
+	End
+	
+	Method SelectMessageContainer(containerName:String,serverName:String)
+		Local foundNode:=GetMessageContainerNode(containerName,serverName)
+		If foundNode Then serverTree.NodeClicked(foundNode)
+	End
+	
+	Method UpdateServerTree()
+		serverTree.RootNode.Text="No Server"
+		serverTree.RootNode.Expanded=True
+		
+		If ircHandler.servers.Count()>0 Then
+			serverTree.RootNodeVisible=False
+		Else
+			serverTree.RootNodeVisible=True
+			Return
+		Endif
+		
+		Local serverExists:Bool
+		Local channelExists:Bool
+		Local serverNode:TreeView.Node
+		Local channelNode:TreeView.Node
+		
+		'Scan for old removed nodes
+		For Local s:=Eachin serverTree.RootNode.Children
+			serverExists=False
+			
+			'Does this server still exist?
+			For Local is:=Eachin ircHandler.servers
+				If s.Text=is.name Then
+					serverExists=True
+					
+					'Does it channels exist?
+					For Local c:=Eachin s.Children
+						channelExists=False
+						
+						For Local ic:=Eachin is.messageContainers
+							If c.Text=ic.name Then
+								channelExists=True
+								Exit
+							Endif
+						Next
+						
+						If Not channelExists Then
+							'Remove the channel
+							c.Remove()
+						Endif
+					Next
+					
+					Exit
+				Endif
+			Next
+			
+			'Well, does it exist?
+			If Not serverExists Then
+				'NOPE! Remove it and all channels
+				s.Remove()
+			Endif
+		Next
+		
+		'Look for new servers and channels
+		For Local is:=Eachin ircHandler.servers
+			serverExists=False
+			
+			'Does this exist in the tree view already?
+			For Local s:=Eachin serverTree.RootNode.Children
+				If is.name=s.Text Then
+					serverNode=s
+					serverExists=True
+					Exit
+				Endif
+			Next
+			
+			'...Well?
+			If Not serverExists Then
+				'NOPE! Add it to the tree view
+				serverNode=New TreeView.Node(is.name,serverTree.RootNode)
+				serverNode.Expanded=True
+			Endif
+			
+			'Are all the channels there?
+			For Local ic:=Eachin is.messageContainers
+				channelExists=False
+				
+				For Local c:=Eachin serverNode.Children
+					If ic.name=c.Text Then
+						channelNode=c
+						channelExists=True
+						Exit
+					Endif
+				Next
+				
+				If Not channelExists Then
+					channelNode=New TreeView.Node(ic.name,serverNode)
+					serverNode.Expanded=True
+				Endif
+			Next
+		Next
+		
+		If Not selectedMessageContainer And serverTree.RootNode.Children.Length>0 Then
+			selectedTreeNode=serverTree.RootNode.Children[0]
+			serverTree.NodeClicked(selectedTreeNode)
+		Endif
+	End
+	
+	Method UpdateUsers()
+		userList.RemoveAllItems()
+		If Not selectedMessageContainer Then Return
+		For Local u:=Eachin selectedMessageContainer.users
+			userList.AddItem(u.name)
+		Next
+		
+		'Update own interface username
+		If selectedServer Then
+			nicknameLabel.Text=selectedServer.nickname+":"
+		Endif
+	End
+	
+	Method UpdateHistory()
+		historyField.Clear()
+		If Not selectedMessageContainer Then Return
+		For Local m:=Eachin selectedMessageContainer.messages
+			AddChatMessage(m)
+		Next
+	End
+	
+	Method AddChatMessage(message:IRCMessage)
+		historyField.AppendText("["+message.time+"]~t"+message.text+"~n")
+	End
+	
+	Method OnRender(canvas:Canvas) Override
+		Super.OnRender(canvas)
+		
+		If selectedMessageContainer Then
+			'Hide or show topic field or
+			If topicField.Text<>selectedMessageContainer.topic Then
+				topicField.Text=selectedMessageContainer.topic
+			Endif
+			' always hide topic to get more useful space
+			topicField.Visible=False '(topicField.Text.Length>0)
+			
+			'Hide or show user list
+			If selectedMessageContainer.users.Count()>0 Then
+				userList.Visible=True
+			Else
+				userList.Visible=False
+			Endif
+		Else
+			topicField.Visible=False
+			userList.Visible=False
+		Endif
+	End
+End
+
+Class IRCIntroView Extends DockingView
+	Field parent:IRCView
+	Field text:String
+	Field introLabel:Label
+	Field servers:=New List<IRCServer>
+	Field roomScroller:ScrollableView
+	Field nickBox:DockingView
+	Field nickField:TextField
+	Field nickLabel:Label
+	Field checkboxes:=New List<CheckButton>
+	Field connectButton:Button
+	
+	Field OnConnect:Void()
+	Field OnNickChange:Void(nick:String)
+	
+	Property Text:String()
+		Return text
+	Setter(str:String)
+		text=str
+	End
+	
+	Property IsConnected:Bool()
+		Return _connected
+	End
+	
+	Method New(owner:IRCView)
+		Super.New()
+		Self.parent=owner
+	End
+	
+	Method AddOnlyServer:IRCServer( nickname:String,name:String,server:String,port:Int,rooms:String )
+		
+		servers.Clear()
+		
+		Local nS:IRCServer
+		
+		For Local s:=Eachin servers
+			If name.ToLower()=s.name.ToLower() Then
+				Notify("Name already exists","A server with this name already exists")
+				Return Null
+			Endif
+		Next
+		
+		nS=New IRCServer(nickname,name,server,port,False)
+		nS.AutoJoinRooms+=rooms
+		
+		servers.AddLast(nS)
+		UpdateInterface()
+		parent.ContentView=Self
+		Return nS
+	End
+	
+	Method AddServer:IRCServer(nickname:String,name:String,server:String,port:Int)
+		Local nS:IRCServer
+	
+		For Local s:=Eachin servers
+			If name.ToLower()=s.name.ToLower() Then
+				Notify("Name already exists","A server with this name already exists")
+				Return Null
+			Endif
+		Next
+		
+		nS=New IRCServer(nickname,name,server,port,False)
+		
+		servers.AddLast(nS)
+		UpdateInterface()
+		parent.ContentView=Self
+		Return nS
+	End
+	
+	Method UpdateInterface()
+		
+		Self.RemoveAllViews()
+		
+		introLabel=New Label(text)
+		introLabel.TextGravity=New Vec2f(0.5,0.5)
+		Self.AddView(introLabel,"top")
+		
+		nickBox=New DockingView
+		nickBox.Layout="fill-y"
+		Self.AddView(nickBox,"top")
+		
+		nickLabel=New Label("Nickname")
+		nickBox.AddView(nickLabel,"left")
+		
+		nickField=New TextField
+		nickField.BlockCursor=False
+		nickField.TextChanged+=Lambda()
+			OnNickChange(nickField.Text)
+		End
+		nickBox.ContentView=nickField
+		
+		roomScroller=New ScrollableView
+		roomScroller.Layout="fill-y"
+		Self.ContentView=roomScroller
+		
+		For Local s:=Eachin Self.servers
+			If Not nickField.Text And s.nickname Then nickField.Text=s.nickname
+			Local chans:=s.AutoJoinRooms.Split("#")
+			For Local c:=Eachin chans
+				If Not c Then Continue
+				checkboxes.AddLast(New CheckButton(s.name+" - #"+c))
+				checkboxes.Last.Checked=True
+				roomScroller.AddView(checkboxes.Last,"top")
+			Next
+		Next
+		
+		connectButton=New Button("Connect")
+		roomScroller.AddView(connectButton,"top")
+		connectButton.Clicked=Lambda()
+			Connect()
+		End
+	End
+	
+	Method Connect()
+		
+		'Is nickname set?
+		If Not nickField.Text Notify( "","Please, enter your nickname." ) ; Return
+		
+		'Local server:=AddServer( _nick,_server,_server,_port )
+		'If server Then server.AutoJoinRooms+=_rooms
+		
+		Local serv:String
+		Local chan:String
+		Local didHaveChans:Bool
+		
+		'Reset all auto join  strings
+		For Local s:=Eachin servers
+			s.AutoJoinRooms=Null
+		Next
+		
+		'Loop throught all checkboxes and get the server and channel 
+		For Local c:=Eachin Self.checkboxes
+			If Not c.Checked Then Continue 'Skip boxes that aren't checked
+			serv=c.Text.Split(" - ")[0].ToLower()
+			chan=c.Text.Split(" - ")[1].ToLower()
+			For Local s:=Eachin servers
+				If serv=s.name.ToLower() Then
+					didHaveChans=True
+					s.AutoJoinRooms+=chan
+				Endif
+			Next
+		Next
+		
+		'Did we have ANY channels to join?
+		If Not didHaveChans Then Return
+		
+		'Send all servers
+		For Local s:=Eachin servers
+			If Not s.AutoJoinRooms Then Continue
+			Local serv:=parent.AddServer(nickField.Text,s.name,s.serverAddress,s.serverPort)
+			serv.AutoJoinRooms=s.AutoJoinRooms
+		Next
+		
+		'Set the parent IRC view to display the chat screen now
+		parent.ContentView=parent.chatScreen
+		
+		_connected=True
+		OnConnect()
+	End
+	
+	
+	Private
+	
+	Field _connected:Bool
+	
+End
+
+#rem
+'=EXAMPLE=
+Class MyWindow Extends Window
+	Field ircView:IRCView
+	
+	Method New(title:String="IRC Test",width:Int=1280*0.85,height:Int=720*0.85,flags:WindowFlags=WindowFlags.Resizable)
+		Super.New(title,width,height,flags)
+		
+		'Create our IRC view
+		ircView=New IRCView
+		ContentView=ircView
+		
+		'Add a new server and connect to it
+		Local nick:="M2_TestUser"
+		Local desc:="freenode"
+		Local server:="irc.freenode.net"'"irc.du.se"
+		Local port:=6667
+		Local serv:IRCServer
+		
+		'Add a server to our intro scren
+		serv=ircView.introScreen.AddServer(nick,desc,server,port)
+		
+		'Select title for our intro screen
+		ircView.introScreen.Text="koreIRC Example"
+		
+		'You can skip the intro screen by adding the server directly to the IRC view
+		'serv=ircView.AddServer(nick,desc,server,port)
+		
+		'Add rooms to connect to at start
+		If serv Then
+			serv.AutoJoinRooms+="#monkey2#heztest"
+			'serv.AutoJoinRooms+="#heztest"
+			
+			'You'll want to update the intro interface after adding rooms
+			ircView.introScreen.UpdateInterface()
+		Endif
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+		App.RequestRender()
+	End
+End
+#end

+ 23 - 2
view/ProjectBrowserView.monkey2

@@ -2,7 +2,7 @@
 Namespace ted2go
 
 
-Class ProjectBrowserView Extends FileBrowser
+Class ProjectBrowserView Extends FileBrowserExt
 
 	
 	Method New( rootPath:String )
@@ -10,7 +10,7 @@ Class ProjectBrowserView Extends FileBrowser
 		Super.New( rootPath )
 		
 		RootNode.Text=StripDir( rootPath )+" ("+rootPath+")"
-		RootNode.Icon=ThemeImages.Get( "project/package.png" )
+		UpdateRootIcon()
 		
 		NodeExpanded+=Lambda( node:TreeView.Node )
 			
@@ -23,6 +23,12 @@ Class ProjectBrowserView Extends FileBrowser
 			
 			If node = RootNode And node.Expanded Then Refresh( True ) ' TRUE - need to refresh icons
 		End
+		
+		App.Activated+=Lambda()
+			New Fiber( Lambda()
+				Refresh()
+			End )
+		End
 	End
 	
 	Method Refresh( update:Bool=True )
@@ -113,6 +119,21 @@ Class ProjectBrowserView Extends FileBrowser
 		Return _filters
 	End
 	
+	Method OnThemeChanged() Override
+		
+		Super.OnThemeChanged()
+		UpdateRootIcon()
+		Self.Refresh(True)
+	End
+	
+	Method UpdateRootIcon()
+		If Prefs.MainProjectIcons Then 'Only load icon if settings say so
+			RootNode.Icon=ThemeImages.Get( "project/package.png" )
+		Else
+			RootNode.Icon=Null
+		Endif
+	End
+	
 	Enum FilterType
 		Equals=0,
 		Starts=1,

+ 30 - 5
view/ProjectView.monkey2

@@ -160,16 +160,41 @@ Class ProjectView Extends ScrollView
 					
 					menu.AddSeparator()
 					
-					menu.AddAction( "Update module" ).Triggered=Lambda()
+					menu.AddAction( "Update / Rebuild "+name ).Triggered=Lambda()
 						
-						_builder.BuildModules( False,name )
+						_builder.BuildModules( True,name )
 					End
 					
-					menu.AddAction( "Rebuild module" ).Triggered=Lambda()
+				Endif
 				
-						_builder.BuildModules( True,name )
+				' update all modules
+				Local path2:=MainWindow.ModsPath
+				If path2.EndsWith( "/" ) Then path2=path2.Slice( 0,path2.Length-1 )
+				
+				If path = path2
+					
+					menu.AddSeparator()
+					
+					menu.AddAction( "Update / Rebuild modules" ).Triggered=Lambda()
+					
+						_builder.BuildModules( False )
 					End
+					
+				Endif
 				
+				' bananas showcase
+				If IsBananasShowcaseAvailable()
+					path2=Prefs.MonkeyRootPath+"bananas"
+					If path = path2
+					
+						menu.AddSeparator()
+					
+						menu.AddAction( "Open bananas showcase" ).Triggered=Lambda()
+					
+							MainWindow.ShowBananasShowcase()
+						End
+					
+					Endif
 				Endif
 				
 			Case FileType.File
@@ -292,7 +317,7 @@ Class ProjectView Extends ScrollView
 	
 	Field _docs:DocumentManager
 	Field _docker:=New DockingView
-	Field _projects:=New StringMap<FileBrowser>
+	Field _projects:=New StringMap<FileBrowserExt>
 	Field _builder:IModuleBuilder
 
 	Method OnOpenProject()

+ 25 - 0
view/ScrollableViewExt.monkey2

@@ -0,0 +1,25 @@
+
+Namespace ted2go
+
+
+' Make mouse wheel's scrolling little faster
+
+Class ScrollableViewExt Extends ScrollableView
+	
+	
+	Protected
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+	
+			Case EventType.MouseWheel
+	
+				Scroll=Scroll+New Vec2i( 0, -RenderStyle.Font.Height*event.Wheel.y*3 )
+	
+			Default
+				
+				Super.OnMouseEvent( event )
+		End
+	End
+End

+ 1 - 1
view/StatusBarView.monkey2

@@ -20,7 +20,7 @@ Class StatusBarView Extends DockingView
 		' DoubleClick
 		_labelIns.DoubleClicked+=Lambda()
 		
-			MainWindow.OverrideTextMode=Not MainWindow.OverrideTextMode
+			MainWindow.OverwriteTextMode=Not MainWindow.OverwriteTextMode
 		End
 		
 		_labelLineInfo=New Label( "0 : 0")