Browse Source

Added build products settings to ted2.

Mark Sibly 9 years ago
parent
commit
1b2d8bf452

+ 32 - 24
src/ted2/buildactions.monkey2

@@ -26,6 +26,7 @@ Class BuildActions
 
 	Field buildAndRun:Action
 	Field build:Action
+	Field buildSettings:Action
 	Field nextError:Action
 	Field lockBuildFile:Action
 	Field updateModules:Action
@@ -33,7 +34,7 @@ Class BuildActions
 	Field moduleManager:Action
 	Field rebuildHelp:Action
 	
-	Field settingsMenu:Menu
+	Field targetMenu:Menu
 	
 	Method New( docs:DocumentManager,console:Console,debugView:DebugView )
 	
@@ -63,6 +64,9 @@ Class BuildActions
 		build.Triggered=OnBuild
 		build.HotKey=Key.F6
 		
+		buildSettings=New Action( "Build Settings" )
+		buildSettings.Triggered=OnBuildFileSettings
+		
 		nextError=New Action( "Next Error" )
 		nextError.Triggered=OnNextError
 		nextError.HotKey=Key.F4
@@ -104,8 +108,6 @@ Class BuildActions
 		group=New CheckGroup
 		_desktopTarget=New CheckButton( "Desktop",,group )
 		_desktopTarget.Layout="fill-x"
-		_consoleTarget=New CheckButton( "Console",,group )
-		_consoleTarget.Layout="fill-x"
 		_emscriptenTarget=New CheckButton( "Emscripten",,group )
 		_emscriptenTarget.Layout="fill-x"
 		_androidTarget=New CheckButton( "Android",,group )
@@ -115,9 +117,6 @@ Class BuildActions
 		_desktopTarget.Clicked+=Lambda()
 			_buildTarget="desktop"
 		End
-		_consoleTarget.Clicked+=Lambda()
-			_buildTarget="console"
-		End
 		_emscriptenTarget.Clicked+=Lambda()
 			_buildTarget="emscripten"
 		End
@@ -129,15 +128,14 @@ Class BuildActions
 		End
 		_buildTarget="desktop"
 		
-		settingsMenu=New Menu( "Build Settings..." )
-		settingsMenu.AddView( _debugConfig )
-		settingsMenu.AddView( _releaseConfig )
-		settingsMenu.AddSeparator()
-		settingsMenu.AddView( _desktopTarget )
-		settingsMenu.AddView( _consoleTarget )
-		settingsMenu.AddView( _emscriptenTarget )
-		settingsMenu.AddView( _androidTarget )
-		settingsMenu.AddView( _iosTarget )
+		targetMenu=New Menu( "Build Target..." )
+		targetMenu.AddView( _debugConfig )
+		targetMenu.AddView( _releaseConfig )
+		targetMenu.AddSeparator()
+		targetMenu.AddView( _desktopTarget )
+		targetMenu.AddView( _emscriptenTarget )
+		targetMenu.AddView( _androidTarget )
+		targetMenu.AddView( _iosTarget )
 
 	End
 	
@@ -172,8 +170,6 @@ Class BuildActions
 		If jobj.Contains( "buildTarget" )
 			_buildTarget=jobj["buildTarget"].ToString()
 			Select _buildTarget
-			Case "console"
-				_consoleTarget.Checked=True
 			Case "emscripten"
 				_emscriptenTarget.Checked=True
 			Case "android"
@@ -222,7 +218,6 @@ Class BuildActions
 
 	Field _debugConfig:CheckButton
 	Field _releaseConfig:CheckButton
-	Field _consoleTarget:CheckButton
 	Field _desktopTarget:CheckButton
 	Field _emscriptenTarget:CheckButton
 	Field _androidTarget:CheckButton
@@ -380,13 +375,18 @@ Class BuildActions
 		Local buildDoc:=BuildDoc()
 		If Not buildDoc Return False
 		
+		Local product:=BuildProduct.GetBuildProduct( buildDoc.Path,target,False )
+		If Not product Return False
+		
+		Local opts:=product.GetMx2ccOpts()
+		
 		Local appType:="gui"
 		If target="console"
 			appType="console"
 			target="desktop"
 		End
 
-		Local cmd:=_mx2cc+" makeapp -build"
+		Local cmd:=_mx2cc+" makeapp -build "+opts
 		cmd+=" -apptype="+appType+" "
 		cmd+=" -config="+config
 		cmd+=" -target="+target
@@ -419,16 +419,16 @@ Class BuildActions
 		Return True
 	End
 	
-	Method OnBuild()
-	
-		BuildApp( _buildConfig,_buildTarget,False )
-	End
-	
 	Method OnBuildAndRun()
 
 		BuildApp( _buildConfig,_buildTarget,True )
 	End
 	
+	Method OnBuild()
+	
+		BuildApp( _buildConfig,_buildTarget,False )
+	End
+	
 	Method OnNextError()
 	
 		While Not _errors.Empty And _errors.First.removed
@@ -459,6 +459,14 @@ Class BuildActions
 		
 	End
 	
+	Method OnBuildFileSettings()
+
+		Local buildDoc:=BuildDoc()
+		If Not buildDoc Return
+		
+		local product:=BuildProduct.GetBuildProduct( buildDoc.Path,_buildTarget,True )
+	End
+	
 	Method OnUpdateModules()
 	
 		BuildModules( False )

+ 459 - 0
src/ted2/buildproduct.monkey2

@@ -0,0 +1,459 @@
+
+Namespace ted2
+
+Class BuildProduct
+
+	Property ProductDir:String()
+
+		Return _productDir
+	End
+
+	Method GetMx2ccOpts:String()
+	
+		Return OnGetMx2ccOpts()
+	End
+	
+	Function GetBuildProduct:BuildProduct( srcPath:String,target:String,edit:Bool )
+	
+		Local product:BuildProduct
+		
+		Select target
+		Case "desktop"
+#if __TARGET__="windows"
+			product=New WindowsProduct( srcPath )
+#else if __TARGET__="macos"
+			product=New MacosProduct( srcPath )
+#else if __TARGET__="linux"
+			product=New LinuxProduct( srcPath )
+#endif
+		Case "emscripten"
+			product=New EmscriptenProduct( srcPath )
+		Case "android"
+			product=New AndroidProduct( srcPath )
+		Case "ios"
+			product=New IosProduct( srcPath )
+		End
+		
+		If Not product Return Null
+		
+		product.LoadVars()
+
+		If GetFileType( product.ProductDir )=FileType.Directory
+		
+			If edit And Not product.EditVars() Return Null
+			
+		Else
+
+			If Not product.EditVars() Return Null
+			
+		Endif
+		
+		If GetFileType( product.ProductDir )<>FileType.Directory
+		
+			If Not CreateDir( product.ProductDir )
+				Alert( "Failed to create product directory '"+product.ProductDir+"'" )
+				Return Null
+			Endif
+			
+			product.OnCreateProduct()
+
+		Endif
+		
+		Return product
+	End
+
+	Protected
+	
+	Method New( srcPath:String,target:String )
+	
+		_srcPath=srcPath
+		_target=target
+		_productsDir=StripExt( _srcPath )+".products/"
+		_productDir=_productsDir+_target.Capitalize()+"/"
+
+'		AddVar( "Product Location",StripExt( srcPath )+".products/"+_target.Capitalize()+"/","directory" )
+	End
+	
+	Method OnGetMx2ccOpts:String() Virtual
+		Return ""
+	End
+	
+	Method OnCreateProduct() Virtual
+	End
+	
+	Method AddExts( exts:String[] )
+	
+		For Local ext:=Eachin exts
+			_exts[ext]=True
+		Next
+	End
+	
+	Method AddVar( name:String,value:String,type:String="string" )
+	
+		_pvars.Push( New ProductVar( name,value,type ) )
+	End
+	
+	Method GetVar:String( name:String )
+	
+		Return _vars[name]
+	End
+	
+	Method ReplaceVars:String( src:String )
+	
+		Local i0:=0
+		
+		Repeat
+			Local i1:=src.Find( "${",i0 )
+			If i1=-1 Exit
+			
+			Local i2:=src.Find( "}",i1+2 )
+			If i2=-1 Exit
+			
+			Local id:=src.Slice( i1+2,i2 )
+			If Not _vars.Contains( id )
+				i0=i2+1
+				Continue
+			Endif
+			
+			Local r:=_vars[id]
+			src=src.Slice( 0,i1 )+r+src.Slice( i2+1 )
+			i0=i1+r.Length
+		Forever
+
+		Return src
+		
+	End
+	
+	Method CopyTemplate:Bool( src:String,dst:String )
+
+		Select GetFileType( src )
+		
+		Case FileType.File
+		
+			If _exts.Contains( ExtractExt( src ).ToLower() )
+			
+				Local tmp:=LoadString( src )
+				
+				tmp=ReplaceVars( tmp )
+				
+				If Not SaveString( tmp,dst ) Return False
+			
+			Else
+			
+				If Not CopyFile( src,dst ) Return False
+		
+			Endif
+			
+		Case FileType.Directory
+		
+			If Not CreateDir( dst ) Return False
+		
+			For Local f:=Eachin LoadDir( src )
+			
+				If Not CopyTemplate( src+"/"+f,dst+"/"+f ) Return False
+
+			Next
+				
+		End
+		
+		Return true
+	End
+
+	Private
+
+	Field _srcPath:String
+	Field _target:String
+	Field _productDir:String
+	Field _productsDir:String
+	Field _exts:=New StringMap<Bool>
+	Field _vars:=New StringMap<String>
+	Field _pvars:=New Stack<ProductVar>
+	
+	Method EditVars:Bool()
+	
+		Local save:=New StringMap<String>
+		
+		For Local pvar:=Eachin _pvars
+			save[pvar.name]=pvar.value
+		End
+	
+		Local opts:=New EditProductDialog( "Build settings for "+StripDir( _srcPath )+" for "+_target.Capitalize()+" target ",_pvars )
+		
+		If Not opts.Run()
+			For Local pvar:=Eachin _pvars
+				pvar.value=save[pvar.name]
+			Next
+			Return False
+		Endif
+		
+		UpdateVars()
+		
+		Local changed:Bool
+		For Local pvar:=Eachin _pvars
+			If pvar.value=save[pvar.name] Continue
+			changed=True
+			Exit
+		Next
+		
+		Local fail:=False
+		
+		If changed And GetFileType( ProductDir )=FileType.Directory
+			Select TextDialog.Run( "Edit Build Settings","Directory at '"+ProductDir+"' already exists.",New String[]( "Overwrite","Cancel" ) )
+			Case 0
+				If Not DeleteDir( ProductDir,True )
+					Alert( "Failed to delete directory '"+ProductDir+"'" )
+					fail=True
+				Endif
+			Case 1
+				fail=True
+			End
+		Endif
+		
+		If fail
+			For Local pvar:=Eachin _pvars
+				pvar.value=save[pvar.name]
+			Next
+			UpdateVars()
+			Return False
+		Endif
+		
+		SaveVars()
+		Return True
+	End
+
+	Method FindVar:ProductVar( name:String )
+	
+		For Local pvar:=Eachin _pvars
+			If pvar.name=name Return pvar
+		Next
+		
+		Return Null
+	End
+	
+	Method SaveVars( jobj:JsonObject )
+	
+		For Local pvar:=Eachin _pvars
+			jobj[pvar.name]=New JsonString( pvar.value )
+		Next
+
+	End
+	
+	Method LoadVars( jobj:JsonObject )
+	
+		For Local it:=Eachin jobj.ToObject()
+			Local pvar:=FindVar( it.Key )
+			If pvar pvar.value=it.Value.ToString()
+		Next
+
+		UpdateVars()
+	End
+	
+	Method UpdateVars()
+
+		_vars.Clear()
+
+		For Local pvar:=Eachin _pvars
+			Local id:=pvar.name.ToUpper().Replace( " ","_" )
+			_vars[id]=pvar.value
+		Next
+	End
+	
+	Method SaveVars()
+	
+		Local jprods:=JsonObject.Load( _productsDir+"products.json" )
+		If Not jprods jprods=New JsonObject
+		
+		Local jvars:=New JsonObject
+		SaveVars( jvars )
+		
+		jprods[_target]=jvars
+		
+		CreateDir( _productsDir )
+		SaveString( jprods.ToJson(),_productsDir+"products.json" )
+	End
+	
+	Method LoadVars()
+		
+		Local jprods:=JsonObject.Load( _productsDir+"products.json" )
+		If Not jprods Return
+		
+		Local jvars:=Cast<JsonObject>( jprods[_target] )
+		If jvars LoadVars( jvars )
+	End
+	
+End
+
+Class DesktopProduct Extends BuildProduct
+
+	Method New( srcPath:String,target:String )
+		Super.New( srcPath,target )
+		
+		AddVar( "Application Name","Monkey 2 Game" )
+		AddVar( "Application Type","gui","options:gui|console" )
+	End
+	
+	Protected
+
+	Method OnCreateProduct() Override
+	
+		Local appName:=GetVar( "APPLICATION_NAME" )
+		Local appType:=GetVar( "APPLICATION_TYPE" )
+		
+		CreateDir( ProductDir+"assets" )
+	End
+	
+	Method OnGetMx2ccOpts:String() Override
+
+		Local appName:=GetVar( "APPLICATION_NAME" )
+		Local appType:=GetVar( "APPLICATION_TYPE" )
+		
+		Local opts:=""
+		opts+=" ~q-product="+ProductDir+appName+"~q"
+		opts+=" -apptype="+appType
+		
+		Return opts
+	End
+	
+End
+
+Class WindowsProduct Extends DesktopProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"windows" )
+	End
+
+End
+
+Class MacosProduct Extends DesktopProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"macos" )
+	End
+	
+	Protected
+	
+	Method OnCreateProduct() Override
+	
+		Local appType:=GetVar( "APPLICATION_TYPE" )
+		
+		If appType<>"gui"
+			CreateDir( ProductDir+"assets" )
+			Return
+		Endif
+
+		Local appName:=GetVar( "APPLICATION_NAME" )
+		Local appDir:=ProductDir+appName+".app/"
+
+		CreateDir( appDir )
+		CreateDir( appDir+"Contents" )
+		CreateDir( appDir+"Contents/MacOS" )
+		CreateDir( appDir+"Contents/Resources" )
+					
+		Local plist:=""
+		plist+="<?xml version=~q1.0~q encoding=~qUTF-8~q?>~n"
+		plist+="<!DOCTYPE plist PUBLIC ~q-//Apple Computer//DTD PLIST 1.0//EN~q ~qhttp://www.apple.com/DTDs/PropertyList-1.0.dtd~q>~n"
+		plist+="<plist version=~q1.0~q>~n"
+		plist+="<dict>~n"
+		plist+="~t<key>CFBundleExecutable</key>~n"
+		plist+="~t<string>"+appName+"</string>~n"
+		plist+="~t<key>CFBundleIconFile</key>~n"
+		plist+="~t<string>"+appName+"</string>~n"
+		plist+="~t<key>CFBundlePackageType</key>~n"
+		plist+="~t<string>APPL</string>~n"
+		plist+="</dict>~n"
+		plist+="</plist>~n"
+					
+		SaveString( plist,appDir+"Contents/Info.plist" )
+	End
+
+End
+
+Class LinuxProduct Extends DesktopProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"linux" )
+	End
+End
+
+Class EmscriptenProduct Extends BuildProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"emscripten" )
+
+		AddVar( "Application Name","Monkey 2 Game" )
+	End
+	
+	Protected
+	
+	Method OnCreateProduct() Override
+
+		Local appName:=GetVar( "APPLICATION_NAME" )
+	End
+	
+	Method OnGetMx2ccOpts:String() Override
+
+		Local appName:=GetVar( "APPLICATION_NAME" )
+	
+		Local opts:=""
+		opts+=" ~q-product="+ProductDir+appName+"~q"
+		
+		Return opts
+	End
+
+End
+
+Class AndroidProduct Extends BuildProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"android" )
+		
+		AddExts( New String[]( ".xml",".java",".gradle" ) )
+		
+		AddVar( "Application Name","Monkey 2 Game" )
+		AddVar( "Package Name","com.monkey2.monkey2game" )
+		AddVar( "Activity Name","Monkey2Game" )
+		AddVar( "Screen Orientation","landscape","options:landscape|portrait|user" )
+	End
+
+	Protected
+	
+	Method OnCreateProduct() Override
+
+		Local appName:=GetVar( "APPLICATION_NAME" )
+		Local packageName:=GetVar( "PACKAGE_NAME" )
+		Local activityName:=GetVar( "ACTIVITY_NAME" )
+		Local screenOrientation:=GetVar( "SCREEN_ORIENTATION" )
+		
+		Local mainDir:=ProductDir+"app/src/main/"
+		Local packageDir:=mainDir+"java/"+packageName.Replace( ".","/" )+"/"
+		
+		CopyTemplate( "android/Monkey2Game",StripSlashes( ProductDir ) )
+
+		CreateDir( packageDir )
+		CopyFile( mainDir+"Monkey2Game.java",packageDir+activityName+".java" )
+		DeleteFile( mainDir+"Monkey2Game.java" )
+	
+	End
+	
+	Method OnGetMx2ccOpts:String() Override
+
+		Local opts:=""
+		opts+=" -assets=~q"+ProductDir+"app/src/main/assets/~q"
+		opts+=" -dlls=~q"+ProductDir+"app/src/main/jniLibs/~q"
+		
+		Return opts
+	End
+	
+End
+
+Class IosProduct Extends BuildProduct
+
+	Method New( srcPath:String )
+		Super.New( srcPath,"ios" )
+		
+		AddVar( "Application Name","Monkey 2 Game" )
+		
+		AddVar( "Screen Orientation","landscape","options:landscape|portrait|user" )
+	End
+
+End

+ 1 - 1
src/ted2/debugview.monkey2

@@ -142,7 +142,7 @@ Class DebugView Extends DockingView
 			
 		MainWindow.ShowOutputConsole()
 	
-		Local cmd:=RealPath( appFile )
+		Local cmd:="~q"+RealPath( appFile )+"~q"
 		
 		If config<>"debug"
 		

+ 253 - 0
src/ted2/editproductdialog.monkey2

@@ -0,0 +1,253 @@
+
+Namespace ted2
+
+Class OptionsField Extends DockingView
+
+	Field CurrentChanged:Void()
+
+	Method New( options:String[],current:Int=0 )
+	
+		_options=options
+		
+		_current=current
+		
+		_group=New CheckGroup
+		
+		_group.CheckedChanged+=Lambda()
+		
+			For Local i:=0 Until _checks.Length
+				If _group.Checked=_checks[i]
+					_current=i
+					CurrentChanged()
+					Return
+				Endif
+			Next
+			
+		End
+
+		_checks=New CheckButton[options.Length]
+		
+		For Local i:=0 Until options.Length
+		
+			_checks[i]=New CheckButton( _options[i],,_group )
+			
+			AddView( _checks[i],"left" )
+		
+		Next
+		
+		_checks[_current].Checked=True
+	End
+	
+	Property Current:Int()
+	
+		Return _current
+	
+	Setter( current:Int )
+	
+		_current=current
+		
+		_checks[_current].Checked=True
+	End
+	
+	Private
+
+	Field _options:String[]
+	
+	Field _current:Int
+	
+	Field _group:CheckGroup
+	
+	Field _checks:CheckButton[]
+	
+End
+
+Class FilePathField Extends DockingView
+
+	Field FilePathChanged:Void()
+
+	Method New( path:String="",fileType:FileType=FileType.File )
+	
+		_fileType=fileType
+
+		_textField=New TextField( path )
+		
+		_textField.TextChanged+=Lambda()
+		
+			FilePathChanged()
+		End
+
+		_pathButton=New PushButton( "..." )
+		
+		_pathButton.Clicked=Lambda()
+		
+			New Fiber( Lambda()
+		
+				Local future:=New Future<String>
+			
+				App.Idle+=Lambda()
+					If _fileType=FileType.Directory
+						future.Set( requesters.RequestDir( "Select Directory",_textField.Text ) )
+					Else
+						future.Set( requesters.RequestFile( "Select File",_textField.Text ) )
+					Endif
+				End
+				
+				Local path:=future.Get()
+				If Not path Return
+				
+				_textField.Text=path
+				
+				FilePathChanged()
+			End )
+		End
+		
+		AddView( _pathButton,"right" )
+
+		ContentView=_textField
+
+		MaxSize=New Vec2i( 320,0 )
+	End
+	
+	Property FilePath:String()
+	
+		Return _textField.Text
+	
+	Setter( path:String )
+	
+		_textField.Text=path
+	End
+	
+	Property FileType:FileType()
+	
+		Return _fileType
+	
+	Setter( fileType:FileType )
+	
+		_fileType=fileType
+	End
+	
+	Private
+	
+	Field _textField:TextField
+	Field _pathButton:PushButton
+	Field _fileType:FileType
+
+End
+
+Class ProductVar
+
+	Field name:String
+	Field value:String
+	Field type:String
+		
+	Method New( name:String,value:String,type:String="string" )
+		Self.name=name
+		Self.value=value
+		Self.type=type
+	End
+	
+	Method CreateFieldView:View()
+	
+		Local fieldView:View
+		
+		Select type
+		Case "string"
+		
+			Local view:=New TextField( value )
+			
+			view.TextChanged+=Lambda()
+				value=view.Text
+			End
+			
+			fieldView=view
+			
+		Case "directory"
+		
+			Local view:=New FilePathField( value,FileType.Directory )
+			
+			view.FilePathChanged=Lambda()
+				value=view.FilePath
+			End
+			
+			fieldView=view
+			
+		Default
+		
+			If type.StartsWith( "options:" )
+
+				Local opts:=type.Slice( 8 ).Split( "|" )
+
+				Local view:=New OptionsField( opts )
+
+				view.CurrentChanged+=Lambda()
+					value=opts[view.Current]
+				End
+			
+				fieldView=view
+
+			Endif
+		End
+		
+		Return fieldView
+	End
+		
+End
+
+Class EditProductDialog Extends Dialog
+
+	Method New( title:String,vars:Stack<ProductVar> )
+	
+		Title=title
+		
+		_vars=vars
+		
+		_table=New TableView
+		_table.AddColumn( "Setting" )
+		_table.AddColumn( "Value" )
+		
+		_table.AddRows( _vars.Length )
+		
+		For Local i:=0 Until _vars.Length
+			Local pvar:=_vars[i]
+			
+			_table[0,i]=New Label( pvar.name )
+			_table[1,i]=pvar.CreateFieldView()
+		End
+		
+		ContentView=_table
+		
+		AddAction( "Okay" ).Triggered=Lambda()
+
+			_result.Set( True )
+		End
+		
+		AddAction( "Cancel"  ).Triggered=lambda()
+			
+			_result.Set( False )
+		End
+	End
+	
+	Method Run:Bool()
+	
+		Open()
+		
+		App.BeginModal( Self )
+		
+		Local result:=_result.Get()
+		
+		App.EndModal()
+		
+		Close()
+		
+		Return result
+	End
+	
+	Private
+	
+	Field _table:TableView
+	
+	Field _vars:Stack<ProductVar>
+	
+	Field _result:=New Future<Bool>
+
+End

+ 11 - 2
src/ted2/mainwindow.monkey2

@@ -28,6 +28,11 @@ Class MainWindowInstance Extends Window
 
 		_docsManager.CurrentDocumentChanged+=UpdateKeyView
 		
+		App.FileDropped+=Lambda( path:String )
+			Print "Dropped:"+path
+			_docsManager.OpenDocument( path,True )
+		End
+
 		_docsManager.DocumentAdded+=Lambda( doc:Ted2Document )
 			AddRecentFile( doc.Path )
 			UpdateRecentFilesMenu()
@@ -36,7 +41,7 @@ Class MainWindowInstance Extends Window
 		_docsManager.DocumentRemoved+=Lambda( doc:Ted2Document )
 			If IsTmpPath( doc.Path ) DeleteFile( doc.Path )
 		End
-
+		
 		_buildConsole=New Console
 		_outputConsole=New Console
 		_helpView=New HtmlView
@@ -65,6 +70,7 @@ Class MainWindowInstance Extends Window
 		_tabMenu.AddAction( _fileActions.saveAs )
 		_tabMenu.AddSeparator()
 		_tabMenu.AddAction( _buildActions.lockBuildFile )
+		_tabMenu.AddAction( _buildActions.buildSettings )
 		
 		_docsTabView.RightClicked+=Lambda()
 			_tabMenu.Open()
@@ -147,7 +153,8 @@ Class MainWindowInstance Extends Window
 		_buildMenu=New Menu( "Build" )
 		_buildMenu.AddAction( _buildActions.buildAndRun )
 		_buildMenu.AddAction( _buildActions.build )
-		_buildMenu.AddSubMenu( _buildActions.settingsMenu )
+		_buildMenu.AddAction( _buildActions.buildSettings )
+		_buildMenu.AddSubMenu( _buildActions.targetMenu )
 		_buildMenu.AddSeparator()
 		_buildMenu.AddAction( _buildActions.nextError )
 		_buildMenu.AddSeparator()
@@ -192,6 +199,8 @@ Class MainWindowInstance Extends Window
 		ContentView=_contentView
 
 		LoadState( jobj )
+		
+		Plugin.CreatePlugins()
 
 		App.Idle+=OnAppIdle
 	End

+ 6 - 1
src/ted2/modulemanager.monkey2

@@ -230,9 +230,14 @@ Class ModuleManager Extends Dialog
 
 			Local zip:=module.name+"-v"+module.new_version+".zip"
 			Local dst:=downloadDir+zip
+			
+			If Not DeleteDir( "modules/"+module.name,True )
+				Alert( "Error deleting module directory '"+module.name+"'" )
+				Return False
+			End
 		
 			If Not ExtractZip( dst,"modules",module.name+"/" )
-				Alert( "Error extracting zip '"+dst+"'" )
+				Alert( "Error extracting zip to '"+dst+"'" )
 				Return False
 			Endif
 			

+ 39 - 12
src/ted2/monkey2document.monkey2

@@ -31,7 +31,7 @@ Function InitKeywords()
 	kws+="For;To;Step;Next;"
 	kws+="Select;Case;Default;"
 	kws+="Try;Catch;Throw;Throwable;"
-	kws+="Return;Print;Static;Cast"
+	kws+="Return;Print;Static;Cast;Extension"
 	
 	For Local kw:=Eachin kws.Split( ";" )
 		Keywords[kw.ToLower()]=kw
@@ -45,19 +45,38 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 	Local icolor:=0
 	Local istart:=sol
 	Local preproc:=False
+
+	'comment nest
+	'
+	Local cnest:=state & 255
+	If cnest=255 cnest=-1
+	
+	'block string flag
+	'	
+	Local blkstr:=(state & 256)=0
 	
-	If state>-1 icolor=COLOR_COMMENT
+	If cnest<>-1 
+		icolor=COLOR_COMMENT
+	Else If blkstr
+		icolor=COLOR_STRING
+	Endif
 	
 	While i0<eol
 	
 		Local start:=i0
 		Local chr:=text[i0]
 		i0+=1
+		
 		If IsSpace( chr ) Continue
 		
+		If blkstr
+			If chr=34 blkstr=False
+			Continue
+		Endif
+		
 		If chr=35 And istart=sol
 			preproc=True
-			If state=-1 icolor=COLOR_PREPROC
+			If cnest=-1 icolor=COLOR_PREPROC
 			Continue
 		Endif
 		
@@ -71,11 +90,11 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 			
 			Select id.ToLower()
 			Case "rem"
-				state+=1
+				cnest+=1
 				icolor=COLOR_COMMENT
 			Case "end"
-				If state>-1 
-					state-=1
+				If cnest<>-1
+					cnest-=1
 					icolor=COLOR_COMMENT
 				Endif
 			End
@@ -84,13 +103,14 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 		
 		Endif
 		
-		If state>-1 Or preproc Exit
+		If cnest<>-1 Or preproc Exit
 		
 		Local color:=icolor
 		
 		If chr=39
 		
 			i0=eol
+			
 			color=COLOR_COMMENT
 			
 		Else If chr=34
@@ -98,7 +118,11 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 			While i0<eol And text[i0]<>34
 				i0+=1
 			Wend
-			If i0<eol i0+=1
+			If i0<eol
+				i0+=1
+			Else
+				blkstr=True
+			Endif
 			
 			color=COLOR_STRING
 			
@@ -114,9 +138,9 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 			
 				Select id.ToLower()
 				Case "rem"				
-					state+=1
+					cnest+=1
 				Case "end"
-					state=Max( state-1,-1 )
+					cnest=Max( cnest-1,-1 )
 				End
 				
 				icolor=COLOR_COMMENT
@@ -168,8 +192,11 @@ Function Monkey2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,s
 		colors[i]=icolor
 	Next
 	
+	state=cnest & 255
+	
+	If Not blkstr state|=256
+	
 	Return state
-
 End
 
 Public
@@ -230,7 +257,7 @@ Class Monkey2DocumentView Extends Ted2TextView
 		Local cursor:=Cursor
 		
 		Local state:=Document.LineState( Document.FindLine( cursor ) )
-		If state<>-1 Return
+		If state & 255 <> 255 Return
 		
 		Local text:=Text
 		Local start:=cursor

+ 1 - 1
src/ted2/plaintextdocument.monkey2

@@ -81,7 +81,7 @@ Class PlainTextDocumentType Extends Ted2DocumentType
 	Method New()
 		AddPlugin( Self )
 		
-		Extensions=New String[]( ".h",".hpp",".hxx",".c",".cpp",".cxx",".m",".mm",".s",".asm",".html",".js",".css",".php",".md",".xml",".ini",".sh",".bat",".glsl",".txt" )
+		Extensions=New String[]( ".h",".hpp",".hxx",".c",".cpp",".cxx",".m",".mm",".s",".asm",".java",".html",".js",".css",".php",".md",".xml",".ini",".sh",".bat",".glsl",".txt",".gradle" )
 	End
 	
 	Method OnCreateDocument:Ted2Document( path:String ) Override

+ 14 - 1
src/ted2/plugin.monkey2

@@ -7,12 +7,21 @@ Class Plugin
 	
 		Return "<unititled plugin>"
 	End
-
+	
 	Function PluginsOfType<T>:T[]() Where T Extends Plugin
 		
 		Return Plugins<T>.Plugins().ToArray()
 	End
 	
+	'***** INTERNAL *****
+	Function CreatePlugins()
+	
+		For Local plugin:=Eachin PluginsOfType<Plugin>()
+			plugin.OnCreate()
+		Next
+	
+	End
+	
 	Protected
 	
 	Method New()
@@ -24,6 +33,10 @@ Class Plugin
 	
 		Plugins<T>.Plugins().Add( plugin )
 	End
+	
+	Method OnCreate() Virtual
+	
+	End
 
 	Private
 	

+ 5 - 2
src/ted2/ted2.monkey2

@@ -1,5 +1,5 @@
 
-#If __TARGET__="desktop" and __HOSTOS__="windows"
+#If __TARGET__="windows"
 #Import "bin/wget.exe"
 #End
 
@@ -32,6 +32,9 @@
 
 #import "textviewkeyeventfilter"
 
+#Import "buildproduct"
+#Import "editproductdialog"
+
 Namespace ted2
 
 Using std..
@@ -40,7 +43,7 @@ Using mojox..
 
 Function Main()
 
-#if __TARGET__="desktop"
+#if __DESKTOP_TARGET__
 		
 	ChangeDir( AppDir() )