2
0
Эх сурвалжийг харах

Merge pull request #62 from blitz-research/develop

Develop
abakobo 8 жил өмнө
parent
commit
76df05706c
48 өөрчлөгдсөн 3777 нэмэгдсэн , 421 устгасан
  1. 13 7
      src/ted2/modulemanager.monkey2
  2. 102 31
      src/ted2go/MainWindow.monkey2
  3. 52 22
      src/ted2go/Prefs.monkey2
  4. 11 2
      src/ted2go/ProcessReader.monkey2
  5. 73 21
      src/ted2go/Ted2.monkey2
  6. 45 58
      src/ted2go/action/BuildActions.monkey2
  7. 8 1
      src/ted2go/action/EditActions.monkey2
  8. 1 1
      src/ted2go/action/FileActions.monkey2
  9. 7 0
      src/ted2go/action/HelpActions.monkey2
  10. BIN
      src/ted2go/assets/fonts/MesloLGSDZ-Bold.ttf
  11. 3 1
      src/ted2go/assets/themes/default-tmp.json
  12. BIN
      src/ted2go/assets/themes/irc/notice.png
  13. BIN
      src/ted2go/assets/themes/prime_assets/button_skin.png
  14. BIN
      src/ted2go/assets/themes/prime_assets/checkbox_icons.png
  15. BIN
      src/ted2go/assets/themes/prime_assets/dialog_skin.png
  16. BIN
      src/ted2go/assets/themes/prime_assets/progressbar_icons.png
  17. BIN
      src/ted2go/assets/themes/prime_assets/square.png
  18. BIN
      src/ted2go/assets/themes/prime_assets/tabbutton_skin.png
  19. BIN
      src/ted2go/assets/themes/prime_assets/tabclose_icons.png
  20. BIN
      src/ted2go/assets/themes/prime_assets/treeview_icons.png
  21. 50 8
      src/ted2go/assets/themes/ted2-default.json
  22. 455 0
      src/ted2go/assets/themes/theme-prime-base.json
  23. 9 0
      src/ted2go/assets/themes/theme-prime-blue.json
  24. 9 0
      src/ted2go/assets/themes/theme-prime-red.json
  25. 1 1
      src/ted2go/assets/themes/theme-smooth.json
  26. 1 1
      src/ted2go/assets/themes/theme-warm.json
  27. 3 1
      src/ted2go/assets/themes/themes.json
  28. 31 0
      src/ted2go/dialog/DialogExt.monkey2
  29. 231 131
      src/ted2go/dialog/PrefsDialog.monkey2
  30. 187 0
      src/ted2go/dialog/UpdateModulesDialog.monkey2
  31. 426 0
      src/ted2go/document/BananasDocument.monkey2
  32. 60 27
      src/ted2go/document/CodeDocument.monkey2
  33. 4 4
      src/ted2go/document/Ted2Document.monkey2
  34. 1 8
      src/ted2go/eventfilter/Monkey2KeyEventFilter.monkey2
  35. 4 4
      src/ted2go/product/ModuleManager.monkey2
  36. 1 1
      src/ted2go/syntax/Keywords.monkey2
  37. 0 38
      src/ted2go/utils/JsonUtils.monkey2
  38. 23 0
      src/ted2go/utils/Utils.monkey2
  39. 44 0
      src/ted2go/utils/jsonutils.monkey2
  40. 214 0
      src/ted2go/view/CodeMapView.monkey2
  41. 38 41
      src/ted2go/view/CodeTextView.monkey2
  42. 6 4
      src/ted2go/view/ConsoleViewExt.monkey2
  43. 243 0
      src/ted2go/view/FileBrowserExt.monkey2
  44. 1342 0
      src/ted2go/view/IRCView.monkey2
  45. 23 2
      src/ted2go/view/ProjectBrowserView.monkey2
  46. 30 5
      src/ted2go/view/ProjectView.monkey2
  47. 25 0
      src/ted2go/view/ScrollableViewExt.monkey2
  48. 1 1
      src/ted2go/view/StatusBarView.monkey2

+ 13 - 7
src/ted2/modulemanager.monkey2

@@ -203,13 +203,19 @@ Class ModuleManager Extends Dialog
 			Local dst:=downloadDir+zip
 			Local dst:=downloadDir+zip
 
 
 #if __HOSTOS__="macos"
 #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
 #Else
-			Local cmd:="wget -O ~q"+dst+"~q ~q"+src+"~q"
+			Local cmd:="wget -q -O ~q"+dst+"~q ~q"+src+"~q"
 #Endif
 #Endif
 			_progress.Text="Downloading "+zip+"..."
 			_progress.Text="Downloading "+zip+"..."
 			
 			
-			If Not _console.Run( cmd ) Return False
+			Print "CD="+CurrentDir()
+			Print "cmd="+cmd
+			
+			If Not _console.Run( cmd )
+				Print "FALSE!"
+				Return False
+			Endif
 			
 			
 			If _console.ExitCode
 			If _console.ExitCode
 				Alert( "Process '"+cmd+"' failed with exit code "+_console.Process.ExitCode )
 				Alert( "Process '"+cmd+"' failed with exit code "+_console.Process.ExitCode )
@@ -428,10 +434,10 @@ Class ModuleManager Extends Dialog
 		progress.Open()
 		progress.Open()
 		
 		
 #if __HOSTOS__="macos"
 #if __HOSTOS__="macos"
-		Local cmd:="curl -o ~q"+tmp+"~q ~q"+src+"~q"
-#else
-		Local cmd:="wget -O ~q"+tmp+"~q ~q"+src+"~q"
-#endif
+		Local cmd:="curl -s -o ~q"+tmp+"~q ~q"+src+"~q"
+#Else
+		Local cmd:="wget -q -O ~q"+tmp+"~q ~q"+src+"~q"
+#Endif
 		If Not _console.Run( cmd )
 		If Not _console.Run( cmd )
 		
 		
 			progress.Close()
 			progress.Close()

+ 102 - 31
src/ted2go/MainWindow.monkey2

@@ -15,7 +15,10 @@ Namespace ted2go
 Global MainWindow:MainWindowInstance
 Global MainWindow:MainWindowInstance
 
 
 Class MainWindowInstance Extends Window
 Class MainWindowInstance Extends Window
-
+	
+	Field SizeChanged:Void()
+	Field Rendered:Void()
+	
 	Method New( title:String,rect:Recti,flags:WindowFlags,jobj:JsonObject )
 	Method New( title:String,rect:Recti,flags:WindowFlags,jobj:JsonObject )
 		Super.New( title,rect,flags )
 		Super.New( title,rect,flags )
 		
 		
@@ -52,7 +55,15 @@ Class MainWindowInstance Extends Window
 			If IsTmpPath( doc.Path ) DeleteFile( doc.Path )
 			If IsTmpPath( doc.Path ) DeleteFile( doc.Path )
 			SaveState()
 			SaveState()
 		End
 		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
 		'Build tab
 		
 		
 		_buildConsole=New ConsoleExt
 		_buildConsole=New ConsoleExt
@@ -174,7 +185,7 @@ Class MainWindowInstance Extends Window
 		_findActions=New FindActions( _docsManager,_projectView,_findConsole )
 		_findActions=New FindActions( _docsManager,_projectView,_findConsole )
 		_helpActions=New HelpActions
 		_helpActions=New HelpActions
 		_viewActions=New ViewActions( _docsManager )
 		_viewActions=New ViewActions( _docsManager )
-
+		
 		_tabMenu=New Menu
 		_tabMenu=New Menu
 		_tabMenu.AddAction( _fileActions.close )
 		_tabMenu.AddAction( _fileActions.close )
 		_tabMenu.AddAction( _fileActions.closeOthers )
 		_tabMenu.AddAction( _fileActions.closeOthers )
@@ -299,8 +310,6 @@ Class MainWindowInstance Extends Window
 		_buildMenu.AddAction( _buildActions.lockBuildFile )
 		_buildMenu.AddAction( _buildActions.lockBuildFile )
 		_buildMenu.AddSeparator()
 		_buildMenu.AddSeparator()
 		_buildMenu.AddAction( _buildActions.updateModules )
 		_buildMenu.AddAction( _buildActions.updateModules )
-		_buildMenu.AddAction( _buildActions.rebuildModules )
-		_buildMenu.AddSeparator()
 		_buildMenu.AddAction( _buildActions.moduleManager )
 		_buildMenu.AddAction( _buildActions.moduleManager )
 		
 		
 		'Window menu
 		'Window menu
@@ -322,6 +331,7 @@ Class MainWindowInstance Extends Window
 		_helpMenu=New MenuExt( "Help" )
 		_helpMenu=New MenuExt( "Help" )
 		_helpMenu.AddAction( _helpActions.quickHelp )
 		_helpMenu.AddAction( _helpActions.quickHelp )
 		_helpMenu.AddAction( _helpActions.viewManuals )
 		_helpMenu.AddAction( _helpActions.viewManuals )
+		If IsBananasShowcaseAvailable() Then _helpMenu.AddAction( _helpActions.bananas )
 		_helpMenu.AddSeparator()
 		_helpMenu.AddSeparator()
 		_helpMenu.AddAction( _buildActions.rebuildHelp )
 		_helpMenu.AddAction( _buildActions.rebuildHelp )
 		_helpMenu.AddSeparator()
 		_helpMenu.AddSeparator()
@@ -355,6 +365,7 @@ Class MainWindowInstance Extends Window
 		_consolesTabView.AddTab( "Output",_outputConsoleView,False )
 		_consolesTabView.AddTab( "Output",_outputConsoleView,False )
 		_consolesTabView.AddTab( "Docs",_helpConsole,False )
 		_consolesTabView.AddTab( "Docs",_helpConsole,False )
 		_consolesTabView.AddTab( "Find",_findConsole,False )
 		_consolesTabView.AddTab( "Find",_findConsole,False )
+		_consolesTabView.AddTab( "Chat",_ircView,False )
 		
 		
 		_statusBar=New StatusBarView
 		_statusBar=New StatusBarView
 		
 		
@@ -373,12 +384,22 @@ Class MainWindowInstance Extends Window
 		
 		
 		App.Idle+=OnAppIdle
 		App.Idle+=OnAppIdle
 		
 		
-		If GetFileType( "bin/ted2.state.json" )=FileType.None _helpActions.about.Trigger()
+		CheckFirstStart()
 		
 		
 		_enableSaving=True
 		_enableSaving=True
 		
 		
 	End
 	End
 	
 	
+	Field PrefsChanged:Void()
+	Method OnPrefsChanged()
+		
+		ArrangeElements()
+		PrefsChanged()
+		
+		SetupChatTab()
+		
+	End
+	
 	Method ArrangeElements()
 	Method ArrangeElements()
 		
 		
 		_contentView.RemoveView( _toolBar )
 		_contentView.RemoveView( _toolBar )
@@ -396,14 +417,15 @@ Class MainWindowInstance Extends Window
 		Local location:=Prefs.MainProjectTabsRight ? "right" Else "left"
 		Local location:=Prefs.MainProjectTabsRight ? "right" Else "left"
 		
 		
 		Local size:=_browsersTabView.Rect.Width
 		Local size:=_browsersTabView.Rect.Width
-		If size=0 Then size=250
+		If size=0 Then size=300
 		_contentView.AddView( _browsersTabView,location,size,True )
 		_contentView.AddView( _browsersTabView,location,size,True )
 		
 		
 		size=_consolesTabView.Rect.Height
 		size=_consolesTabView.Rect.Height
-		If size=0 Then size=200
+		If size=0 Then size=150
 		_contentView.AddView( _consolesTabView,"bottom",size,True )
 		_contentView.AddView( _consolesTabView,"bottom",size,True )
 		
 		
 		_contentView.ContentView=_docsTabView
 		_contentView.ContentView=_docsTabView
+		
 	End
 	End
 	
 	
 	
 	
@@ -441,7 +463,7 @@ Class MainWindowInstance Extends Window
 		Return _modsDir
 		Return _modsDir
 	End
 	End
 	
 	
-	Property OverrideTextMode:Bool()
+	Property OverwriteTextMode:Bool()
 	
 	
 		Return _ovdMode
 		Return _ovdMode
 	Setter( value:Bool )
 	Setter( value:Bool )
@@ -476,21 +498,21 @@ Class MainWindowInstance Extends Window
 	
 	
 	Property AboutPagePath:String()
 	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
 		Return path
 	End
 	End
 	
 	
 	Method Terminate()
 	Method Terminate()
 	
 	
 		_isTerminating=True
 		_isTerminating=True
-		
 		SaveState()
 		SaveState()
-		
-		_fileActions.closeAll.Trigger() 'stops all parser's timers on close docs
-		
+		_enableSaving=False
+		OnForceStop() ' kill build process if started
+		ProcessReader.StopAll()
+
 		App.Terminate()
 		App.Terminate()
 	End
 	End
 
 
@@ -676,6 +698,15 @@ Class MainWindowInstance Extends Window
 		
 		
 	End
 	End
 	
 	
+	Method CheckFirstStart()
+		
+		If GetFileType( "bin/ted2.state.json" )=FileType.None
+			_helpActions.about.Trigger()
+			ShowBananasShowcase()
+		Endif
+	End
+	
+	
 	Public
 	Public
 	
 	
 	Method ShowProjectView()
 	Method ShowProjectView()
@@ -785,6 +816,10 @@ Class MainWindowInstance Extends Window
 		_helpTree.Update()
 		_helpTree.Update()
 	End
 	End
 	
 	
+	Method ShowBananasShowcase()
+		OpenDocument( Prefs.MonkeyRootPath+"bananas/!showcase/all.bananas" )
+	End
+	
 	Method ReadError( path:String )
 	Method ReadError( path:String )
 		Alert( "I/O Error reading file '"+path+"'" )
 		Alert( "I/O Error reading file '"+path+"'" )
 	End
 	End
@@ -893,9 +928,10 @@ Class MainWindowInstance Extends Window
 		SaveString( jobj.ToJson(),"bin/ted2.state.json" )
 		SaveString( jobj.ToJson(),"bin/ted2.state.json" )
 	End
 	End
 
 
-	Method OpenDocument( path:String )
+	Method OpenDocument( path:String,lockIt:Bool=False )
 	
 	
 		_docsManager.OpenDocument( path,True )
 		_docsManager.OpenDocument( path,True )
+		If lockIt Then _buildActions.LockBuildFile()
 	End
 	End
 	
 	
 	Method GetActionFind:Action()
 	Method GetActionFind:Action()
@@ -925,7 +961,16 @@ Class MainWindowInstance Extends Window
 			_inited=True
 			_inited=True
 			OnInit()
 			OnInit()
 		Endif
 		Endif
+		
 		Super.OnRender( canvas )
 		Super.OnRender( canvas )
+		
+		If _resized
+			_resized=False
+			SizeChanged()
+		Endif
+		
+		Rendered()
+		Rendered=Null
 	End
 	End
 	
 	
 	Method OnInit()
 	Method OnInit()
@@ -934,15 +979,34 @@ Class MainWindowInstance Extends Window
 		_docsTabView.EnsureVisibleCurrentTab()
 		_docsTabView.EnsureVisibleCurrentTab()
 	End
 	End
 	
 	
-	Method OnCloseApp()
+	Method OnAppClose()
 		
 		
-		SaveState()
-		_enableSaving=False
-		OnForceStop() ' kill build process if started
-		ProcessReader.StopAll()
 		_fileActions.quit.Trigger()
 		_fileActions.quit.Trigger()
 	End
 	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 )
 	Method LoadState( jobj:JsonObject )
 	
 	
 		If jobj.Contains( "browserSize" )
 		If jobj.Contains( "browserSize" )
@@ -1025,12 +1089,17 @@ Class MainWindowInstance Extends Window
 	Method OnWindowEvent( event:WindowEvent ) Override
 	Method OnWindowEvent( event:WindowEvent ) Override
 
 
 		Select event.Type
 		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
-	
 	End
 	End
 	
 	
 	
 	
@@ -1050,6 +1119,7 @@ Class MainWindowInstance Extends Window
 	Field _helpActions:HelpActions
 	Field _helpActions:HelpActions
 	Field _viewActions:ViewActions
 	Field _viewActions:ViewActions
 	
 	
+	Field _ircView:IRCView
 	Field _buildConsole:ConsoleExt
 	Field _buildConsole:ConsoleExt
 	Field _outputConsole:ConsoleExt
 	Field _outputConsole:ConsoleExt
 	Field _outputConsoleView:DockingView
 	Field _outputConsoleView:DockingView
@@ -1061,7 +1131,8 @@ Class MainWindowInstance Extends Window
 	Field _docBrowser:DockingView
 	Field _docBrowser:DockingView
 	Field _debugView:DebugView
 	Field _debugView:DebugView
 	Field _helpTree:HelpTreeView
 	Field _helpTree:HelpTreeView
-
+	
+	'Field _ircTabView:TabView
 	Field _docsTabView:TabViewExt
 	Field _docsTabView:TabViewExt
 	Field _consolesTabView:TabView
 	Field _consolesTabView:TabView
 	Field _browsersTabView:TabView
 	Field _browsersTabView:TabView
@@ -1100,9 +1171,9 @@ Class MainWindowInstance Extends Window
 	Field _consoleVisibleCounter:=0
 	Field _consoleVisibleCounter:=0
 	Field _isTerminating:Bool
 	Field _isTerminating:Bool
 	Field _enableSaving:Bool
 	Field _enableSaving:Bool
+	Field _resized:Bool
 	Field _browsersSize:=0,_consolesSize:=0
 	Field _browsersSize:=0,_consolesSize:=0
-	
-	
+
 	
 	
 	Method ToJson:JsonValue( rect:Recti )
 	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 ) ) )
 		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
src/ted2go/Prefs.monkey2

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

+ 11 - 2
src/ted2go/ProcessReader.monkey2

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

+ 73 - 21
src/ted2go/Ted2.monkey2

@@ -33,6 +33,7 @@
 #Import "dialog/DialogExt"
 #Import "dialog/DialogExt"
 #Import "dialog/NoTitleDialog"
 #Import "dialog/NoTitleDialog"
 #Import "dialog/FindInFilesDialog"
 #Import "dialog/FindInFilesDialog"
+#Import "dialog/UpdateModulesDialog"
 
 
 #Import "document/DocumentManager"
 #Import "document/DocumentManager"
 #Import "document/Ted2Document"
 #Import "document/Ted2Document"
@@ -42,6 +43,7 @@
 #Import "document/AudioDocument"
 #Import "document/AudioDocument"
 #Import "document/JsonDocument"
 #Import "document/JsonDocument"
 #Import "document/XmlDocument"
 #Import "document/XmlDocument"
+#Import "document/BananasDocument"
 
 
 #Import "eventfilter/TextViewKeyEventFilter"
 #Import "eventfilter/TextViewKeyEventFilter"
 #Import "eventfilter/Monkey2KeyEventFilter"
 #Import "eventfilter/Monkey2KeyEventFilter"
@@ -71,12 +73,15 @@
 #Import "utils/JsonUtils"
 #Import "utils/JsonUtils"
 #Import "utils/Utils"
 #Import "utils/Utils"
 
 
+#Import "view/IRCView"
+#Import "view/CodeMapView"
 #Import "view/CodeTextView"
 #Import "view/CodeTextView"
 #Import "view/ConsoleViewExt"
 #Import "view/ConsoleViewExt"
 #Import "view/ListViewExt"
 #Import "view/ListViewExt"
 #Import "view/AutocompleteView"
 #Import "view/AutocompleteView"
 #Import "view/CodeTreeView"
 #Import "view/CodeTreeView"
 #Import "view/TreeViewExt"
 #Import "view/TreeViewExt"
+#Import "view/FileBrowserExt"
 #Import "view/CodeGutterView"
 #Import "view/CodeGutterView"
 #Import "view/ToolBarViewExt"
 #Import "view/ToolBarViewExt"
 #Import "view/HintView"
 #Import "view/HintView"
@@ -93,6 +98,7 @@
 #Import "view/Monkey2TreeView"
 #Import "view/Monkey2TreeView"
 #Import "view/GutterView"
 #Import "view/GutterView"
 #Import "view/MenuExt"
 #Import "view/MenuExt"
+#Import "view/ScrollableViewExt"
 
 
 #Import "MainWindow"
 #Import "MainWindow"
 #Import "Plugin"
 #Import "Plugin"
@@ -111,7 +117,7 @@ Using tinyxml2..
 
 
 Const MONKEY2_DOMAIN:="http://monkeycoder.co.nz"
 Const MONKEY2_DOMAIN:="http://monkeycoder.co.nz"
 
 
-Global AppTitle:="Ted2Go v2.3.2"
+Global AppTitle:="Ted2Go v2.4"
 
 
 
 
 Function Main()
 Function Main()
@@ -141,7 +147,7 @@ Function Main()
 	Prefs.LoadState( jobj )
 	Prefs.LoadState( jobj )
 	
 	
 	'initial theme
 	'initial theme
-	'	
+	'
 	If Not jobj.Contains( "theme" ) jobj["theme"]=New JsonString( "theme-classic-dark" )
 	If Not jobj.Contains( "theme" ) jobj["theme"]=New JsonString( "theme-classic-dark" )
 	If Not jobj.Contains( "themeScale" ) jobj["themeScale"]=New JsonNumber( 1 )
 	If Not jobj.Contains( "themeScale" ) jobj["themeScale"]=New JsonNumber( 1 )
 	
 	
@@ -151,7 +157,7 @@ Function Main()
 	config["initialThemeScale"]=jobj.GetNumber( "themeScale" )
 	config["initialThemeScale"]=jobj.GetNumber( "themeScale" )
 	
 	
 	'start the app!
 	'start the app!
-	'	
+	'
 	New AppInstance( config )
 	New AppInstance( config )
 	
 	
 	'initial window state
 	'initial window state
@@ -163,8 +169,8 @@ Function Main()
 	If jobj.Contains( "windowRect" ) 
 	If jobj.Contains( "windowRect" ) 
 		rect=ToRecti( jobj["windowRect"] )
 		rect=ToRecti( jobj["windowRect"] )
 	Else
 	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 )
 		rect=New Recti( 0,0,w,h )
 		flags|=WindowFlags.Center
 		flags|=WindowFlags.Center
 	Endif
 	Endif
@@ -182,7 +188,7 @@ Function Main()
 	Next
 	Next
 	
 	
 	App.Run()
 	App.Run()
-		
+	
 End
 End
 
 
 Function SetupMonkeyRootPath:String( rootPath:String,searchMode:Bool )
 Function SetupMonkeyRootPath:String( rootPath:String,searchMode:Bool )
@@ -192,23 +198,22 @@ Function SetupMonkeyRootPath:String( rootPath:String,searchMode:Bool )
 	ChangeDir( rootPath )
 	ChangeDir( rootPath )
 	
 	
 	If searchMode
 	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
 		Wend
 		
 		
-		rootPath=CurrentDir()
+		rootPath=found
 	Else
 	Else
 		
 		
 		Local ok:= (GetFileType( "bin" )=FileType.Directory And GetFileType( "modules" )=FileType.Directory)
 		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
 	Return GetFileType( path ) = FileType.File
 End
 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
src/ted2go/action/BuildActions.monkey2

@@ -26,7 +26,7 @@ End
 
 
 Interface IModuleBuilder
 Interface IModuleBuilder
 	
 	
-	Method BuildModules:Bool( clean:Bool,modules:String="" )
+	Method BuildModules:Bool( clean:Bool,modules:String="",configs:String="debug release" )
 	
 	
 End
 End
 
 
@@ -41,7 +41,6 @@ Class BuildActions Implements IModuleBuilder
 	Field nextError:Action
 	Field nextError:Action
 	Field lockBuildFile:Action
 	Field lockBuildFile:Action
 	Field updateModules:Action
 	Field updateModules:Action
-	Field rebuildModules:Action
 	Field moduleManager:Action
 	Field moduleManager:Action
 	Field rebuildHelp:Action
 	Field rebuildHelp:Action
 	
 	
@@ -110,16 +109,11 @@ Class BuildActions Implements IModuleBuilder
 		lockBuildFile.HotKey=Key.L
 		lockBuildFile.HotKey=Key.L
 		lockBuildFile.HotKeyModifiers=Modifier.Menu
 		lockBuildFile.HotKeyModifiers=Modifier.Menu
 		
 		
-		updateModules=New Action( "Update modules..." )
+		updateModules=New Action( "Update / Rebuild modules..." )
 		updateModules.Triggered=OnUpdateModules
 		updateModules.Triggered=OnUpdateModules
 		updateModules.HotKey=Key.U
 		updateModules.HotKey=Key.U
 		updateModules.HotKeyModifiers=Modifier.Menu
 		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=New Action( "Module manager..." )
 		moduleManager.Triggered=OnModuleManager
 		moduleManager.Triggered=OnModuleManager
 		
 		
@@ -220,6 +214,11 @@ Class BuildActions Implements IModuleBuilder
 		Return _locked
 		Return _locked
 	End
 	End
 	
 	
+	Method LockBuildFile()
+		
+		OnLockBuildFile()
+	End
+	
 	Method SaveState( jobj:JsonObject )
 	Method SaveState( jobj:JsonObject )
 		
 		
 		If _locked jobj["lockedDocument"]=New JsonString( _locked.Path )
 		If _locked jobj["lockedDocument"]=New JsonString( _locked.Path )
@@ -288,50 +287,42 @@ Class BuildActions Implements IModuleBuilder
 		buildAndRun.Enabled=canbuild
 		buildAndRun.Enabled=canbuild
 		nextError.Enabled=Not _errors.Empty
 		nextError.Enabled=Not _errors.Empty
 		updateModules.Enabled=idle
 		updateModules.Enabled=idle
-		rebuildModules.Enabled=idle
 		rebuildHelp.Enabled=idle
 		rebuildHelp.Enabled=idle
 		moduleManager.Enabled=idle
 		moduleManager.Enabled=idle
 	End
 	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
 		Next
-	
-		targets.Push( "All!" )
-		targets.Push( "Cancel" )
-	
+		
+		time=Millisecs()-time
 		Local prefix:=clean ? "Rebuild" Else "Update"
 		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
 		If result
 			_console.Write( "~n"+prefix+" modules completed successfully!~n" )
 			_console.Write( "~n"+prefix+" modules completed successfully!~n" )
 		Else
 		Else
 			_console.Write( "~n"+prefix+" modules failed.~n" )
 			_console.Write( "~n"+prefix+" modules failed.~n" )
 		Endif
 		Endif
-	
+		_console.Write( "Total time elapsed: "+FormatTime( time )+".~n" )
+		
 		Return result
 		Return result
 	End
 	End
 	
 	
@@ -360,6 +351,7 @@ Class BuildActions Implements IModuleBuilder
 	Field _validTargets:StringStack
 	Field _validTargets:StringStack
 	Field _timing:Long
 	Field _timing:Long
 	
 	
+	
 	Method BuildDoc:CodeDocument()
 	Method BuildDoc:CodeDocument()
 		
 		
 		If Not _locked Return Cast<CodeDocument>( _docs.CurrentDocument )
 		If Not _locked Return Cast<CodeDocument>( _docs.CurrentDocument )
@@ -400,7 +392,7 @@ Class BuildActions Implements IModuleBuilder
 		tv.GotoLine( err.line )
 		tv.GotoLine( err.line )
 	End
 	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()
 		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.")
 		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 )
 		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 )
 		MainWindow.ShowStatusBarText( status )
 		
 		
 		Return _console.ExitCode=0
 		Return _console.ExitCode=0
 	End
 	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
 		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
 			Local cmd:=MainWindow.Mx2ccPath+" makemods -target="+target
 			If clean cmd+=" -clean"
 			If clean cmd+=" -clean"
@@ -512,7 +505,7 @@ Class BuildActions Implements IModuleBuilder
 	
 	
 	Method MakeDocs:Bool()
 	Method MakeDocs:Bool()
 	
 	
-		Return BuildMx2( MainWindow.Mx2ccPath+" makedocs","Rebuilding documentation..." )
+		Return BuildMx2( MainWindow.Mx2ccPath+" makedocs","Rebuilding documentation...","build",True )
 	End
 	End
 	
 	
 	Method BuildApp:Bool( config:String,target:String,sourceAction:String )
 	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 title := sourceAction="build" ? "Building" Else (sourceAction="run" ? "Running" Else "Checking")
 		Local msg:=title+" ~ "+target+" ~ "+config+" ~ "+StripDir( buildDoc.Path )
 		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.")
 		_console.Write("~nDone.")
 		
 		
@@ -619,6 +612,7 @@ Class BuildActions Implements IModuleBuilder
 	Method OnLockBuildFile()
 	Method OnLockBuildFile()
 	
 	
 		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
 		Local doc:=Cast<CodeDocument>( _docs.CurrentDocument )
+		
 		If Not doc Return
 		If Not doc Return
 		
 		
 		If _locked _locked.State=""
 		If _locked _locked.State=""
@@ -648,13 +642,6 @@ Class BuildActions Implements IModuleBuilder
 		BuildModules( False )
 		BuildModules( False )
 	End
 	End
 	
 	
-	Method OnRebuildModules()
-	
-		If _console.Running Return
-	
-		BuildModules( True )
-	End
-	
 	Method OnModuleManager()
 	Method OnModuleManager()
 	
 	
 		If _console.Running Return
 		If _console.Running Return

+ 8 - 1
src/ted2go/action/EditActions.monkey2

@@ -125,7 +125,14 @@ Class EditActions
 	
 	
 		Local tv:=Cast<TextView>( App.KeyView )
 		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
 	
 	
 End
 End

+ 1 - 1
src/ted2go/action/FileActions.monkey2

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

+ 7 - 0
src/ted2go/action/HelpActions.monkey2

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

BIN
src/ted2go/assets/fonts/MesloLGSDZ-Bold.ttf


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

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

BIN
src/ted2go/assets/themes/irc/notice.png


BIN
src/ted2go/assets/themes/prime_assets/button_skin.png


BIN
src/ted2go/assets/themes/prime_assets/checkbox_icons.png


BIN
src/ted2go/assets/themes/prime_assets/dialog_skin.png


BIN
src/ted2go/assets/themes/prime_assets/progressbar_icons.png


BIN
src/ted2go/assets/themes/prime_assets/square.png


BIN
src/ted2go/assets/themes/prime_assets/tabbutton_skin.png


BIN
src/ted2go/assets/themes/prime_assets/tabclose_icons.png


BIN
src/ted2go/assets/themes/prime_assets/treeview_icons.png


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

@@ -3,10 +3,22 @@
 	
 	
 	"colors":{
 	"colors":{
 
 
+		"transparent": "#0000",
+		"textview-cursor-line":"#2000",
+		"textview-whitespaces":"#2aaa",
 		"statusbar": "content",
 		"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":{
 	"styles":{
 	
 	
 		"GutterView":{
 		"GutterView":{
@@ -96,6 +108,11 @@
 			"extends":"ProgressBar",
 			"extends":"ProgressBar",
 			"margin":[ 6,0 ]
 			"margin":[ 6,0 ]
 		},
 		},
+		"StatusBarButton":{
+			"extends":"ToolButton",
+			"padding":[ 0 ],
+			"skinColor":"transparent"
+		},
 
 
 		"CompletionDialog":{
 		"CompletionDialog":{
 			"extends":"Dialog"
 			"extends":"Dialog"
@@ -109,17 +126,42 @@
 			"backgroundColor":"content"
 			"backgroundColor":"content"
 		},
 		},
 		
 		
-		"StatusBarButton":{
-			"extends":"ToolButton",
-			"padding":[ 0 ],
-			"skinColor":"transparent"
-		},
-		
 		"TextFieldBordered":{
 		"TextFieldBordered":{
 			"extends":"TextField",
 			"extends":"TextField",
 			"border":[ 1 ],
 			"border":[ 1 ],
 			"borderColor":"clear"
 			"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
src/ted2go/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
src/ted2go/assets/themes/theme-prime-blue.json

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

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

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

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

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

+ 1 - 1
src/ted2go/assets/themes/theme-smooth-warm.json → src/ted2go/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",
 	"extends":"ted2-default",
  
  

+ 3 - 1
src/ted2go/assets/themes/themes.json

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

+ 31 - 0
src/ted2go/dialog/DialogExt.monkey2

@@ -12,21 +12,52 @@ Class DialogExt Extends Dialog
 	End
 	End
 	
 	
 	Method Show()
 	Method Show()
+		
 		If _opened Return
 		If _opened Return
 		_opened = True
 		_opened = True
 		Open()
 		Open()
 		OnShow()
 		OnShow()
 	End
 	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()
 	Method Hide()
+	
+		HideWithResult( True )
+	End
+	
+	Method HideWithResult( ok:Bool )
+		
 		If Not _opened Return
 		If Not _opened Return
+		
 		_opened = False
 		_opened = False
 		Close()
 		Close()
 		OnHide()
 		OnHide()
+		
+		If _wait
+			_wait.Set( ok )
+			_wait=Null
+		Endif
 	End
 	End
 	
 	
+	
 	Private
 	Private
 	
 	
 	Field _opened:Bool
 	Field _opened:Bool
+	Field _wait:Future<Bool>
 	
 	
 End
 End

+ 231 - 131
src/ted2go/dialog/PrefsDialog.monkey2

@@ -7,81 +7,128 @@ Class PrefsDialog Extends DialogExt
 	
 	
 	Method New()
 	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=New TextField( Prefs.MonkeyRootPath )
 		_monkeyRootPath.Enabled=False
 		_monkeyRootPath.Enabled=False
 		Local chooseMonkeyPath:=New Action( "..." )
 		Local chooseMonkeyPath:=New Action( "..." )
@@ -91,7 +138,7 @@ Class PrefsDialog Extends DialogExt
 		
 		
 			Local path:=MainWindow.RequestDir( "Choose Monkey2 root folder",initDir )
 			Local path:=MainWindow.RequestDir( "Choose Monkey2 root folder",initDir )
 			If Not path Return
 			If Not path Return
-			
+		
 			' check path
 			' check path
 			Local real:=SetupMonkeyRootPath( path,False )
 			Local real:=SetupMonkeyRootPath( path,False )
 			If real
 			If real
@@ -103,36 +150,134 @@ Class PrefsDialog Extends DialogExt
 				' restore current
 				' restore current
 				ChangeDir( initDir )
 				ChangeDir( initDir )
 			Endif
 			Endif
-			
+		
 		End
 		End
 		Local btnChooseMonkeyPath:=New PushButton( chooseMonkeyPath )
 		Local btnChooseMonkeyPath:=New PushButton( chooseMonkeyPath )
 		
 		
+		Local docker:=New DockingView
 		Local monkeyPathDock:=New DockingView
 		Local monkeyPathDock:=New DockingView
 		monkeyPathDock.AddView( New Label( "Monkey2 root folder" ),"left" )
 		monkeyPathDock.AddView( New Label( "Monkey2 root folder" ),"left" )
 		monkeyPathDock.AddView( _monkeyRootPath,"left" )
 		monkeyPathDock.AddView( _monkeyRootPath,"left" )
 		monkeyPathDock.AddView( btnChooseMonkeyPath,"left" )
 		monkeyPathDock.AddView( btnChooseMonkeyPath,"left" )
 		
 		
-		'----------------------------
-		' put into the form
-		'----------------------------
-		Local docker:=New DockingView
-		
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( monkeyPathDock,"top" )
 		docker.AddView( monkeyPathDock,"top" )
 		
 		
-		docker.AddView( New Label( "------ Main:" ),"top" )
+		docker.AddView( New Label( " " ),"top" )
 		docker.AddView( _mainProjectTabsRight,"top" )
 		docker.AddView( _mainProjectTabsRight,"top" )
+		docker.AddView( _mainProjectIcons,"top" )
 		docker.AddView( _mainToolBarVisible,"top" )
 		docker.AddView( _mainToolBarVisible,"top" )
 		docker.AddView( New Label( " " ),"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( _editorToolBarVisible,"top" )
 		docker.AddView( _editorGutterVisible,"top" )
 		docker.AddView( _editorGutterVisible,"top" )
 		docker.AddView( _editorShowWhiteSpaces,"top" )
 		docker.AddView( _editorShowWhiteSpaces,"top" )
 		docker.AddView( font,"top" )
 		docker.AddView( font,"top" )
 		docker.AddView( _editorShowEvery10LineNumber,"top" )
 		docker.AddView( _editorShowEvery10LineNumber,"top" )
+		docker.AddView( _editorCodeMapVisible,"top" )
+		docker.AddView( _editorAutoIndent,"top" )
 		docker.AddView( New Label( " " ),"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( _acEnabled,"top" )
 		docker.AddView( after,"top" )
 		docker.AddView( after,"top" )
 		docker.AddView( _acUseTab,"top" )
 		docker.AddView( _acUseTab,"top" )
@@ -142,76 +287,31 @@ Class PrefsDialog Extends DialogExt
 		docker.AddView( _acUseDot,"top" )
 		docker.AddView( _acUseDot,"top" )
 		docker.AddView( _acKeywordsOnly,"top" )
 		docker.AddView( _acKeywordsOnly,"top" )
 		docker.AddView( New Label( " " ),"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
 	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
 	
 	
 End
 End

+ 187 - 0
src/ted2go/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
src/ted2go/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
src/ted2go/document/CodeDocument.monkey2

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

+ 4 - 4
src/ted2go/document/Ted2Document.monkey2

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

+ 1 - 8
src/ted2go/eventfilter/Monkey2KeyEventFilter.monkey2

@@ -15,7 +15,7 @@ Class Monkey2KeyEventFilter Extends TextViewKeyEventFilter
 		
 		
 		Local ctrl:=(event.Modifiers & Modifier.Control)
 		Local ctrl:=(event.Modifiers & Modifier.Control)
 		Local shift:=(event.Modifiers & Modifier.Shift)
 		Local shift:=(event.Modifiers & Modifier.Shift)
-			
+		
 		Select event.Type
 		Select event.Type
 		Case EventType.KeyDown
 		Case EventType.KeyDown
 			
 			
@@ -45,13 +45,6 @@ Class Monkey2KeyEventFilter Extends TextViewKeyEventFilter
 						
 						
 					End
 					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
 			
 			
 		End
 		End

+ 4 - 4
src/ted2go/product/ModuleManager.monkey2

@@ -202,9 +202,9 @@ Class ModuleManager Extends Dialog
 			Local dst:=downloadDir+zip
 			Local dst:=downloadDir+zip
 
 
 #if __HOSTOS__="macos"
 #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
 #else
-			Local cmd:="wget -O ~q"+dst+"~q ~q"+src+"~q"
+			Local cmd:="wget -q -O ~q"+dst+"~q ~q"+src+"~q"
 #endif
 #endif
 			_progress.Text="Downloading "+zip+"..."
 			_progress.Text="Downloading "+zip+"..."
 			
 			
@@ -427,9 +427,9 @@ Class ModuleManager Extends Dialog
 		progress.Open()
 		progress.Open()
 		
 		
 #if __HOSTOS__="macos"
 #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
 #else
-		Local cmd:="wget -O ~q"+tmp+"~q ~q"+src+"~q"
+		Local cmd:="wget -q -O ~q"+tmp+"~q ~q"+src+"~q"
 #endif
 #endif
 		If Not _console.Run( cmd )
 		If Not _console.Run( cmd )
 		
 		

+ 1 - 1
src/ted2go/syntax/Keywords.monkey2

@@ -94,7 +94,7 @@ Class KeywordsPlugin Extends PluginDependsOnFileType
 	Method Init()
 	Method Init()
 	
 	
 		Local value:JsonValue
 		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 s := (value<>Null ? value.ToString() Else GetInternal())
 		Local words:=s.Split( ";" )
 		Local words:=s.Split( ";" )
 		_keywords=New Keywords( words )
 		_keywords=New Keywords( words )

+ 0 - 38
src/ted2go/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
src/ted2go/utils/Utils.monkey2

@@ -165,3 +165,26 @@ Class Utils
 	End
 	End
 	
 	
 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
src/ted2go/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
src/ted2go/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
src/ted2go/view/CodeTextView.monkey2

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

+ 6 - 4
src/ted2go/view/ConsoleViewExt.monkey2

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

+ 243 - 0
src/ted2go/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
src/ted2go/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
src/ted2go/view/ProjectBrowserView.monkey2

@@ -2,7 +2,7 @@
 Namespace ted2go
 Namespace ted2go
 
 
 
 
-Class ProjectBrowserView Extends FileBrowser
+Class ProjectBrowserView Extends FileBrowserExt
 
 
 	
 	
 	Method New( rootPath:String )
 	Method New( rootPath:String )
@@ -10,7 +10,7 @@ Class ProjectBrowserView Extends FileBrowser
 		Super.New( rootPath )
 		Super.New( rootPath )
 		
 		
 		RootNode.Text=StripDir( rootPath )+" ("+rootPath+")"
 		RootNode.Text=StripDir( rootPath )+" ("+rootPath+")"
-		RootNode.Icon=ThemeImages.Get( "project/package.png" )
+		UpdateRootIcon()
 		
 		
 		NodeExpanded+=Lambda( node:TreeView.Node )
 		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
 			If node = RootNode And node.Expanded Then Refresh( True ) ' TRUE - need to refresh icons
 		End
 		End
+		
+		App.Activated+=Lambda()
+			New Fiber( Lambda()
+				Refresh()
+			End )
+		End
 	End
 	End
 	
 	
 	Method Refresh( update:Bool=True )
 	Method Refresh( update:Bool=True )
@@ -113,6 +119,21 @@ Class ProjectBrowserView Extends FileBrowser
 		Return _filters
 		Return _filters
 	End
 	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
 	Enum FilterType
 		Equals=0,
 		Equals=0,
 		Starts=1,
 		Starts=1,

+ 30 - 5
src/ted2go/view/ProjectView.monkey2

@@ -160,16 +160,41 @@ Class ProjectView Extends ScrollView
 					
 					
 					menu.AddSeparator()
 					menu.AddSeparator()
 					
 					
-					menu.AddAction( "Update module" ).Triggered=Lambda()
+					menu.AddAction( "Update / Rebuild "+name ).Triggered=Lambda()
 						
 						
-						_builder.BuildModules( False,name )
+						_builder.BuildModules( True,name )
 					End
 					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
 					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
 				Endif
 				
 				
 			Case FileType.File
 			Case FileType.File
@@ -292,7 +317,7 @@ Class ProjectView Extends ScrollView
 	
 	
 	Field _docs:DocumentManager
 	Field _docs:DocumentManager
 	Field _docker:=New DockingView
 	Field _docker:=New DockingView
-	Field _projects:=New StringMap<FileBrowser>
+	Field _projects:=New StringMap<FileBrowserExt>
 	Field _builder:IModuleBuilder
 	Field _builder:IModuleBuilder
 
 
 	Method OnOpenProject()
 	Method OnOpenProject()

+ 25 - 0
src/ted2go/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
src/ted2go/view/StatusBarView.monkey2

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