Browse Source

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

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

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

+ 102 - 31
MainWindow.monkey2

@@ -15,7 +15,10 @@ Namespace ted2go
 Global MainWindow:MainWindowInstance
 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
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
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
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
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
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
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
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
assets/fonts/MesloLGSDZ-Bold.ttf


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

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

BIN
assets/themes/irc/notice.png


BIN
assets/themes/prime_assets/button_skin.png


BIN
assets/themes/prime_assets/checkbox_icons.png


BIN
assets/themes/prime_assets/dialog_skin.png


BIN
assets/themes/prime_assets/progressbar_icons.png


BIN
assets/themes/prime_assets/square.png


BIN
assets/themes/prime_assets/tabbutton_skin.png


BIN
assets/themes/prime_assets/tabclose_icons.png


BIN
assets/themes/prime_assets/treeview_icons.png


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

@@ -3,10 +3,22 @@
 	
 	
 	"colors":{
 	"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
assets/themes/theme-prime-base.json

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

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

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

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

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

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

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

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

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

+ 3 - 1
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
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
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
dialog/UpdateModulesDialog.monkey2

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

+ 426 - 0
document/BananasDocument.monkey2

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

+ 60 - 27
document/CodeDocument.monkey2

@@ -1,3 +1,4 @@
+
 Namespace ted2go
 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
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
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
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
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
utils/JsonUtils.monkey2

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

+ 23 - 0
utils/Utils.monkey2

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

+ 44 - 0
utils/jsonutils.monkey2

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

+ 214 - 0
view/CodeMapView.monkey2

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

+ 38 - 41
view/CodeTextView.monkey2

@@ -9,6 +9,7 @@ Class CodeTextView Extends TextView
 	Field Highlighter:Highlighter
 	Field 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
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
view/FileBrowserExt.monkey2

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

+ 1342 - 0
view/IRCView.monkey2

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

+ 23 - 2
view/ProjectBrowserView.monkey2

@@ -2,7 +2,7 @@
 Namespace ted2go
 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
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
view/ScrollableViewExt.monkey2

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

+ 1 - 1
view/StatusBarView.monkey2

@@ -20,7 +20,7 @@ Class StatusBarView Extends DockingView
 		' DoubleClick
 		' 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")