HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component app.text by barry
expand copy to clipboardexpand
const int WINDOW_WIDTH_DEFAULT = 600
const int WINDOW_HEIGHT_DEFAULT = 440

data OpenFile {
	TextArea text
	char filePath[]
	char tabName[]
	bool unsavedChanges
}

component provides App requires ui.IOLayer coreui, ui.Window, ui.TextField, ui.TextArea, io.Output out, io.File, ui.Button, ui.CheckBox, ui.Layout, ui.Font, os.SystemInfo sysInfo, ui.TabList, data.StringUtil stringUtil, ui.Panel, ui.Label {

	Window window
	Panel mainControlPanel
	Button saveButton
	Button saveAsButton
	Button newButton
	Button closeButton
	CheckBox monoCheck
	CheckBox wrapCheck
	CheckBox numbersCheck
	CheckBox indentCheck

	Panel saveChangesPanel
	Label saveChangesLabel
	Button saveChangesYesButton
	Button saveChangesNoButton
	Button saveChangesCancelButton

	Panel saveAsPanel
	Label saveAsLabel
	Button saveAsSaveButton
	Button saveAsCancelButton
	TextField saveAsPathField

	bool saveAsClose

	Font monoFont
	Font sansFont

	String openFiles[]
	TabList tabs

	OpenFile currentFile
	int fileCount

	Layout mainLayout

	int windowWidth
	int windowHeight

	void closeTab()
		{
		//check if this is the last tab; if so, convert it to "Untitled"
		if (fileCount == 1)
			{
			tabs.update(tabs.getSelected(), "Untitled", currentFile)
			currentFile.filePath = null
			currentFile.tabName = "Untitled"
			currentFile.text.setText("")
			currentFile.unsavedChanges = false
			}
			else
			{
			tabs.remove(tabs.getSelected())
			OpenFile nextFile = tabs.getDetails(tabs.getSelected()).value
			window.addObject(nextFile.text)
			window.remObject(currentFile.text)
			currentFile = nextFile
			currentFile.text.setFocus()
			fileCount --
			}
		}
	
	void saveChanges()
		{
		byte content[] = currentFile.text.getText()
		File fd = new File(currentFile.filePath, File.CREATE)
		fd.write(content)
		fd.close()
		tabs.update(tabs.getSelected(), currentFile.tabName, currentFile)
		}
	
	void openFile(char path[], bool select)
		{
		TextArea newText = new TextArea()
		newText.setFont(monoFont)

		newText.setLineNumbers(true)
		newText.setWordWrap(true)
		newText.setAutoIndent(true)

		mainLayout.add(newText, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.ALIGN_LEFT), 0, tabs))
		newText.setSize(WINDOW_WIDTH_DEFAULT - 20, WINDOW_HEIGHT_DEFAULT - newText.getPosition().y - 10)

		OpenFile newFile = new OpenFile(newText, path, getFileName(path))
		int newID = tabs.add(newFile.tabName, newFile)

		newText.setText(loadFile(path))

		sinkevent AppEvents(newText)

		if (select)
			{
			window.addObject(newText)

			if (currentFile != null)
				{
				window.remObject(currentFile.text)
				}
			
			currentFile = newFile
			tabs.select(newID)
			}
		
		fileCount ++
		}
	
	void newFile(char title[], bool select)
		{
		TextArea newText = new TextArea()
		newText.setFont(monoFont)

		newText.setLineNumbers(true)
		newText.setWordWrap(true)
		newText.setAutoIndent(true)

		mainLayout.add(newText, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.ALIGN_LEFT), 0, tabs))
		newText.setSize(WINDOW_WIDTH_DEFAULT - 20, WINDOW_HEIGHT_DEFAULT - newText.getPosition().y - 10)
		sinkevent AppEvents(newText)

		OpenFile newFile = new OpenFile(newText, null, title)
		int newID = tabs.add(newFile.tabName, newFile)

		if (select)
			{
			window.addObject(newText)

			if (currentFile != null)
				{
				window.remObject(currentFile.text)
				}
			
			currentFile = newFile
			tabs.select(newID)
			}

		fileCount ++
		}

	eventsink AppEvents(EventData ed)
		{
		if (ed.source === coreui && ed.type == IOLayer.[ready])
			{
			startApp()
			}
			else if (ed.source === window && ed.type == Window.[close])
			{
			//TODO: close each tab one at a time, checking for unsaved changes and prompting to save before we close the app
			window.close()
			coreui.shutdown()
			}
			else if (ed.source === window && ed.type == Window.[resize])
			{
			WH newSize = ed.details
			currentFile.text.setSize(newSize.width - 20, newSize.height - currentFile.text.getPosition().y - 10)
			tabs.setWidth(newSize.width - 20)

			windowWidth = newSize.width
			windowHeight = newSize.height
			}
			else if (ed.source === saveButton && ed.type == Button.[click])
			{
			if (currentFile.filePath != null)
				{
				saveChanges()
				currentFile.unsavedChanges = false
				}
				else
				{
				saveAsClose = false
				saveAsPathField.setText(currentFile.filePath)
				window.addObject(saveAsPanel)
				window.remObject(mainControlPanel)
				}
			}
			else if (ed.source === monoCheck && ed.type == CheckBox.[click])
			{
			if (monoCheck.isChecked())
				{
				currentFile.text.setFont(monoFont)
				}
				else
				{
				currentFile.text.setFont(sansFont)
				}
			}
			else if (ed.source === wrapCheck && ed.type == CheckBox.[click])
			{
			currentFile.text.setWordWrap(wrapCheck.isChecked())
			}
			else if (ed.source === numbersCheck && ed.type == CheckBox.[click])
			{
			currentFile.text.setLineNumbers(numbersCheck.isChecked())
			}
			else if (ed.source === indentCheck && ed.type == CheckBox.[click])
			{
			currentFile.text.setAutoIndent(indentCheck.isChecked())
			}
			else if (ed.source === newButton && ed.type == Button.[click])
			{
			TextArea newText = new TextArea()
			newText.setFont(monoFont)

			newText.setLineNumbers(true)
			newText.setWordWrap(true)
			newText.setAutoIndent(true)

			mainLayout.add(newText, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.ALIGN_LEFT), 0, tabs))
			newText.setSize(windowWidth - 20, windowHeight - newText.getPosition().y - 10)
			sinkevent AppEvents(newText)

			OpenFile newFile = new OpenFile(newText, null, "Untitled")

			int id = tabs.add(newFile.tabName, newFile)

			window.addObject(newFile.text)
			window.remObject(currentFile.text)
			currentFile = newFile

			tabs.select(id)

			fileCount ++
			}
			else if (ed.source === saveAsButton && ed.type == Button.[click])
			{
			saveAsClose = false
			saveAsPathField.setText(currentFile.filePath)
			window.addObject(saveAsPanel)
			window.remObject(mainControlPanel)
			}
			else if (ed.source === tabs && ed.type == TabList.[select])
			{
			TabDetails td = ed.details
			OpenFile val = td.value
			tabs.select(td.id)
			val.text.setSize(windowWidth - 20, windowHeight - val.text.getPosition().y - 10)
			window.addObject(val.text)
			window.remObject(currentFile.text)
			currentFile = val
			currentFile.text.setFocus()
			}
			else if (ed.source === tabs && ed.type == TabList.[fileDrop])
			{
			String value = ed.details
			openFile(value.string, true)
			}
			else if (ed.source === closeButton && ed.type == Button.[click])
			{
			if (currentFile.unsavedChanges)
				{
				window.addObject(saveChangesPanel)
				window.remObject(mainControlPanel)
				}
				else
				{
				closeTab()
				}
			}
			else if (ed.source === currentFile.text && ed.type == TextArea.[textChanged])
			{
			currentFile.unsavedChanges = true
			tabs.update(tabs.getSelected(), "$(currentFile.tabName)*", currentFile)
			}
			else if (ed.source === saveChangesCancelButton && ed.type == Button.[click])
			{
			window.addObject(mainControlPanel)
			window.remObject(saveChangesPanel)
			}
			else if (ed.source === saveChangesYesButton && ed.type == Button.[click])
			{
			if (currentFile.filePath != null)
				{
				saveChanges()

				window.addObject(mainControlPanel)
				window.remObject(saveChangesPanel)

				closeTab()
				}
				else
				{
				//the save-as dialog
				saveAsClose = true
				window.addObject(saveAsPanel)
				window.remObject(saveChangesPanel)
				}
			}
			else if (ed.source === saveChangesNoButton && ed.type == Button.[click])
			{
			window.addObject(mainControlPanel)
			window.remObject(saveChangesPanel)

			closeTab()
			}
			else if (ed.source === saveAsSaveButton && ed.type == Button.[click])
			{
			//TODO: need to check if the save-as path is valid
			currentFile.filePath = saveAsPathField.getText()
			saveChanges()
			//check if this was a save-as or a save-as-and-close
			if (saveAsClose)
				{
				closeTab()
				}
			window.addObject(mainControlPanel)
			window.remObject(saveAsPanel)
			}
			else if (ed.source === saveAsCancelButton && ed.type == Button.[click])
			{
			//TODO: anything else to do here??
			window.addObject(mainControlPanel)
			window.remObject(saveAsPanel)
			}
		}
	
	char[] loadFile(char path[])
		{
		File fd = new File(path, File.READ)
		char result[] = fd.read(fd.getSize())
		fd.close()
		return result
		}
	
	char[] normalisePath(char path[])
		{
		return path //TODO
		}
	
	char[] getFileName(char path[])
		{
		String parts[] = stringUtil.rsplit(path, "/")
		if (parts.arrayLength != 0) return parts[1].string
		return path
		}
	
	void startApp()
		{
		window = new Window("text")
		window.setSize(WINDOW_WIDTH_DEFAULT, WINDOW_HEIGHT_DEFAULT)
		window.setResizable(true)
		window.setVisible(true)

		windowWidth = WINDOW_WIDTH_DEFAULT
		windowHeight = WINDOW_HEIGHT_DEFAULT
		
		sinkevent AppEvents(window)

		monoFont = new Font(sysInfo.getSystemFont(true), 15)
		sansFont = new Font(sysInfo.getSystemFont(false), 15)

		mainLayout = new Layout()
		mainLayout.setSize(WINDOW_WIDTH_DEFAULT, WINDOW_HEIGHT_DEFAULT)

		Layout layout = new Layout()
		layout.setSize(WINDOW_WIDTH_DEFAULT, 50)

		mainControlPanel = new Panel()

		saveButton = new Button("Save")
		layout.add(saveButton, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.RIGHT), 10))
		mainControlPanel.addObject(saveButton)
		sinkevent AppEvents(saveButton)

		saveAsButton = new Button("Save As")
		layout.add(saveAsButton, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, saveButton))
		mainControlPanel.addObject(saveAsButton)
		sinkevent AppEvents(saveAsButton)

		newButton = new Button("New")
		layout.add(newButton, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, saveAsButton))
		mainControlPanel.addObject(newButton)
		sinkevent AppEvents(newButton)

		closeButton = new Button("Close")
		layout.add(closeButton, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, newButton))
		mainControlPanel.addObject(closeButton)
		sinkevent AppEvents(closeButton)

		monoCheck = new CheckBox("mono")
		layout.add(monoCheck, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, closeButton))
		mainControlPanel.addObject(monoCheck)
		monoCheck.setChecked(true)
		sinkevent AppEvents(monoCheck)

		wrapCheck = new CheckBox("wrap")
		layout.add(wrapCheck, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, monoCheck))
		mainControlPanel.addObject(wrapCheck)
		wrapCheck.setChecked(true)
		sinkevent AppEvents(wrapCheck)

		numbersCheck = new CheckBox("line #")
		layout.add(numbersCheck, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, wrapCheck))
		mainControlPanel.addObject(numbersCheck)
		numbersCheck.setChecked(true)
		sinkevent AppEvents(numbersCheck)

		indentCheck = new CheckBox("auto-indent")
		layout.add(indentCheck, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, numbersCheck))
		mainControlPanel.addObject(indentCheck)
		indentCheck.setChecked(true)
		sinkevent AppEvents(indentCheck)

		layout.setSize(WINDOW_WIDTH_DEFAULT, saveButton.getPreferredSize().height + 20)
		mainControlPanel.setSize(WINDOW_WIDTH_DEFAULT, saveButton.getPreferredSize().height + 20)

		mainLayout.add(mainControlPanel, new LayoutRule(new byte[](LayoutRule.BELOW), 0))
		window.addObject(mainControlPanel)

		//configure auxilliary panels (save-as)
		saveAsPanel = new Panel()
		saveAsPanel.setSize(WINDOW_WIDTH_DEFAULT, mainControlPanel.getPreferredSize().height)
		layout = new Layout()
		layout.setSize(WINDOW_WIDTH_DEFAULT, saveAsPanel.getPreferredSize().height)

		saveAsCancelButton = new Button("Cancel")
		layout.add(saveAsCancelButton, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.LEFT), 10))
		saveAsPanel.addObject(saveAsCancelButton)
		sinkevent AppEvents(saveAsCancelButton)

		saveAsSaveButton = new Button("Save")
		layout.add(saveAsSaveButton, new LayoutRule(new byte[](LayoutRule.LEFT, LayoutRule.ALIGN_TOP), 10, saveAsCancelButton))
		saveAsPanel.addObject(saveAsSaveButton)
		sinkevent AppEvents(saveAsSaveButton)

		saveAsLabel = new Label("Save as:")
		layout.add(saveAsLabel, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.BELOW), 10))
		saveAsPanel.addObject(saveAsLabel)

		saveAsPathField = new TextField()
		saveAsPathField.setWidth(saveAsSaveButton.getPosition().x - (saveAsLabel.getPosition().x + saveAsLabel.getPreferredSize().width) - 20)
		layout.add(saveAsPathField, new LayoutRule(new byte[](LayoutRule.RIGHT, LayoutRule.ALIGN_TOP), 10, saveAsLabel))
		saveAsPanel.addObject(saveAsPathField)

		//configure auxilliary panels (save-changes)
		saveChangesPanel = new Panel()
		saveChangesPanel.setSize(WINDOW_WIDTH_DEFAULT, mainControlPanel.getPreferredSize().height)
		layout = new Layout()
		layout.setSize(WINDOW_WIDTH_DEFAULT, saveChangesPanel.getPreferredSize().height)

		saveChangesCancelButton = new Button("Cancel")
		layout.add(saveChangesCancelButton, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.LEFT), 10))
		saveChangesPanel.addObject(saveChangesCancelButton)
		sinkevent AppEvents(saveChangesCancelButton)

		saveChangesNoButton = new Button("No")
		layout.add(saveChangesNoButton, new LayoutRule(new byte[](LayoutRule.LEFT, LayoutRule.ALIGN_TOP), 10, saveChangesCancelButton))
		saveChangesPanel.addObject(saveChangesNoButton)
		sinkevent AppEvents(saveChangesNoButton)

		saveChangesYesButton = new Button("Yes")
		layout.add(saveChangesYesButton, new LayoutRule(new byte[](LayoutRule.LEFT, LayoutRule.ALIGN_TOP), 10, saveChangesNoButton))
		saveChangesPanel.addObject(saveChangesYesButton)
		sinkevent AppEvents(saveChangesYesButton)

		saveChangesLabel = new Label("Save changes before closing?")
		layout.add(saveChangesLabel, new LayoutRule(new byte[](LayoutRule.BELOW, LayoutRule.RIGHT), 10))
		saveChangesPanel.addObject(saveChangesLabel)
		sinkevent AppEvents(saveChangesLabel)

		//configure the tab list and text area(s)

		tabs = new TabList()
		mainLayout.add(tabs, new LayoutRule[](new LayoutRule(new byte[](LayoutRule.BELOW), 0, mainControlPanel), new LayoutRule(new byte[](LayoutRule.RIGHT), 10)))
		tabs.setWidth(WINDOW_WIDTH_DEFAULT - 20)
		window.addObject(tabs)
		sinkevent AppEvents(tabs)
		
		if (openFiles == null)
			{
			newFile("Untitled", true)
			}
			else
			{
			for (int i = 0; i < openFiles.arrayLength; i++)
				{
				char thisPath[] = openFiles[i].string

				openFile(thisPath, i == 0)
				}
			}
		
		currentFile.text.setFocus()
		}

	int App:main(AppParam params[])
		{
		if (params.arrayLength > 0)
			{
			openFiles = new String[params.arrayLength]
			for (int i = 0; i < params.arrayLength; i++)
				{
				openFiles[i] = new String(normalisePath(params[i].string))
				}
			}
		
		//initialise the system-level UI framework
		coreui.init()
		
		//listen for startup events from the system
		sinkevent AppEvents(coreui)
		
		//run UI system loop, which blocks until last window closed
		coreui.run()
		
		return 0
		}

}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n app.text -m "reason for update" -u yourUsername
Version 2 by barry
Version 1 (this version) by barry
Notes for this version: Standard Library Initialisation