Notepad.cobra with ZEN style.

"""
Cobra Notepad Sample
by Todd A., Charles Esterbrook

This program is a basic text editor written with Cobra and the Windows Presentation Framework (WPF).
"""

@args -lib: 'C:\Program Files (x86)\ReferenceAssemblies\Microsoft\Framework\.NETFramework\v4.5'
@ref 'WindowsBase'
@ref 'PresentationCore'
@ref 'PresentationFramework'
@ref 'System.Xaml'

use System.ComponentModel
use System.Windows
use System.Windows.Controls
use System.Windows.Controls.Primitives
use System.Windows.Documents
use System.Windows.Input
use System.Windows.Media
use System.Windows.Threading
use Microsoft.Win32


class CustomCommands

	shared

		var increaseFontSize = RoutedCommand('IncreaseFontSize', CustomCommands.getType, _
			InputGestureCollection([KeyGesture(Key.OemPlus, ModifierKeys.Control, 'Ctrl++')]))

		var decreaseFontSize = RoutedCommand('DecreaseFontSize', CustomCommands.getType, _
			InputGestureCollection([KeyGesture(Key.OemMinus, ModifierKeys.Control, 'Ctrl+-')]))

		var resetFontSize = RoutedCommand('ResetFontSize', CustomCommands.getType, _
			InputGestureCollection([KeyGesture(Key.D0, ModifierKeys.Control)]))


sig CanExecuteMethod(sender, e as CanExecuteRoutedEventArgs)

sig ExecutedMethod(sender, e as ExecutedRoutedEventArgs)

sig RoutedMethod(sender, e as RoutedEventArgs)


class MainWindow inherits Window

	var _path as String?
	var _isModified as bool
	var _textBox as TextBox
	var _status as TextBlock
	var _filters = 'All Files|*.*;*|Config|*.conf;*.config|Text|*.txt;*.text'

	cue init
		base.init
		.buildLayout
		.bindCommands
		.path = nil

		listen _textBox.textChanged, ref .textChangeHandler
		listen .loaded, ref .loadedHandler
		listen .closing, ref .closingHandler

	get appName as String
		return 'Cobra Notepad Sample'

	get fileName as String?
		return if(.path, Path.getFileName(.path), 'Untitled')

	pro path as String?
		get
			return _path
		set
			_path = value
			if _path
				fileName = Path.getFileName(_path)
				dirName = Path.getDirectoryName(_path)
				.title = '[.appName] - [fileName] - [dirName]'
			else
				.title = '[.appName] - Untitled'

	pro status as String
		get
			return _status.text ? ''
		set
			_status.text = '[value]  ([DateTime.now])'

	def loadedHandler(sender, e as RoutedEventArgs)
		_textBox.focus
		_textBox.fontFamily = FontFamily('Consolas')

	def closingHandler(sender, e as CancelEventArgs)
		res = .promptUserToSave
		if res == MessageBoxResult.Cancel
			e.cancel = true
			return
		else if res == MessageBoxResult.Yes
			.saveFile(_path to !, _textBox.text to !)

	def showAbout(sender, e as RoutedEventArgs)
		MessageBox.show('Copyright © 2010 Cobra Language. All rights reserved.',
			'About [.appName]', MessageBoxButton.OK)

	def buildLayout
		menu = .push(Menu())

		.pushMenuItem('_File')
		if true
			.addMenuItem('_New', ApplicationCommands.new)
			.addMenuItem('_Open...', ApplicationCommands.open)
			.addMenuItem('_Save', ApplicationCommands.save)
			.addMenuItem('Save As...', ApplicationCommands.saveAs)
			.addSeparator
			.addMenuItem('_Print...', ApplicationCommands.print)
			.addSeparator
			.addMenuItem('_Exit', ApplicationCommands.close)
		.popItem

		.pushMenuItem('_Edit')
		if true
			.addMenuItem('Undo', ApplicationCommands.undo)
			.addMenuItem('Redo', ApplicationCommands.redo)
			.addSeparator
			.addMenuItem('Cut', ApplicationCommands.cut)
			.addMenuItem('Copy', ApplicationCommands.copy)
			.addMenuItem('Paste', ApplicationCommands.paste)
			.addMenuItem('Delete', ApplicationCommands.delete)
			.addSeparator
			.addMenuItem('Select All', ApplicationCommands.selectAll)
		.popItem

		.pushMenuItem('_Format')
		if true
			.pushMenuItem('Font Size')
			if true
				.addMenuItem('Increase', CustomCommands.increaseFontSize, ref .increaseFontHandler)
				.addMenuItem('Decrease', CustomCommands.decreaseFontSize, ref .decreaseFontHandler)
				.addSeparator
				.addMenuItem('Reset', CustomCommands.resetFontSize, ref .resetFontHandler)
			.popItem
			# .addMenuItem('Font...', ... to-do
		.popItem

		.pushMenuItem('_Help')
		if true
			.addMenuItem('About', ref .showAbout)
		.popItem

		.popItem  # main menu

		_textBox = TextBox(acceptsReturn=true, acceptsTab=true)
		_textBox.verticalScrollBarVisibility = ScrollBarVisibility.Auto
		_textBox.horizontalScrollBarVisibility = ScrollBarVisibility.Auto
		listen _textBox.previewMouseWheel, ref .textMouseWheelHandler

		statusBar = Grid()
		_status = TextBlock(text='',
			horizontalAlignment=HorizontalAlignment.Left,
			margin=Thickness(5))
		statusBar.children.add(_status)

		grid = Grid()
		grid.rowDefinitions.add(RowDefinition(height=GridLength.auto))
		grid.rowDefinitions.add(RowDefinition())
		grid.rowDefinitions.add(RowDefinition(height=GridLength.auto))

		.content = grid

		grid.children.add(menu)
		grid.children.add(_textBox)
		grid.children.add(statusBar)

		Grid.setRow(menu, 0)
		Grid.setRow(_textBox, 1)
		Grid.setRow(statusBar, 2)

	## Building menus

	var _itemsControlStack = Stack<of ItemsControl>()

	def push(ic as ItemsControl) as ItemsControl
		_itemsControlStack.push(ic)
		return ic

	def pushMenuItem(header as String) as MenuItem
		mi = .addMenuItem(header, nil, nil)
		.push(mi)
		return mi

	def addMenuItem(header as String, command as RoutedCommand?) as MenuItem
		return .addMenuItem(header, command, nil)

	def addMenuItem(header as String, handler as RoutedMethod?) as MenuItem
		return .addMenuItem(header, nil, handler)

	def addMenuItem(header as String, command as RoutedCommand?, handler as RoutedMethod?) as MenuItem
		mi = MenuItem(header=header, command=command)
		if handler, listen mi.click, RoutedEventHandler(handler)
		.addControl(mi)
		return mi

	def addSeparator
		.addControl(Separator())

	def addControl(c as Control)
		_itemsControlStack.peek.items.add(c)

	def popItem as ItemsControl
		return _itemsControlStack.pop

	## Bind commands

	def bindCommands
		.bindCommand(ApplicationCommands.new to !, ref .defaultCanExecute, ref .newExecuted)
		.bindCommand(ApplicationCommands.open to !, ref .defaultCanExecute, ref .openExecuted)
		.bindCommand(ApplicationCommands.save to !, ref .saveCanExecute, ref .saveExecuted)
		.bindCommand(ApplicationCommands.saveAs to !, ref .defaultCanExecute, ref .saveAsExecuted)
		.bindCommand(ApplicationCommands.print to !, ref .defaultCanExecute, ref .printExecuted)
		.bindCommand(ApplicationCommands.close to !, ref .defaultCanExecute, ref .exitExecuted)

		.bindCommand(ApplicationCommands.undo to !, ref .undoCanExecute, ref .undoExecuted)
		.bindCommand(ApplicationCommands.redo to !, ref .defaultCanExecute, ref .redoExecuted)

		.bindCommand(ApplicationCommands.cut to !, ref .defaultCanExecute, ref .cutExecuted)
		.bindCommand(ApplicationCommands.copy to !, ref .defaultCanExecute, ref .copyExecuted)
		.bindCommand(ApplicationCommands.paste to !, ref .defaultCanExecute, ref .pasteExecuted)

		.bindCommand(ApplicationCommands.delete to !, ref .defaultCanExecute, ref .deleteExecuted)

		.bindCommand(ApplicationCommands.selectAll to !, ref .defaultCanExecute, ref .selectAllExecuted)
		
		.bindCommand(CustomCommands.increaseFontSize, ref .defaultCanExecute, ref .increaseFontHandler)		
		.bindCommand(CustomCommands.decreaseFontSize, ref .defaultCanExecute, ref .decreaseFontExecuted)
		.bindCommand(CustomCommands.resetFontSize, ref .defaultCanExecute, ref .resetFontExecuted)

	def bindCommand(command as ICommand, canExecute as CanExecuteMethod, executed as ExecutedMethod)
		cb = CommandBinding(command)
		listen cb.canExecute, CanExecuteRoutedEventHandler(canExecute)
		listen cb.executed, ExecutedRoutedEventHandler(executed)
		.commandBindings.add(cb)

	def textChangeHandler(sender, e as TextChangedEventArgs)
		if not _isModified
			_isModified = true
			.status = 'Modified'
			
	def increaseFontHandler(sender, e as RoutedEventArgs?)
		_textBox.fontSize += 1f
		if _textBox.fontSize > 24f, _textBox.fontSize = 24f
		.status = 'Increased font size.'
	
	def decreaseFontHandler(sender, e as RoutedEventArgs?)
		_textBox.fontSize -= 1f
		if _textBox.fontSize < 6f, _textBox.fontSize = 6f
		.status = 'Decreased font size.'

	def resetFontHandler(sender, e as RoutedEventArgs?)
		_textBox.fontSize = 12f
		.status = 'Reset font size.'

	def textMouseWheelHandler(sender, e as MouseWheelEventArgs)
		if (Keyboard.modifiers & ModifierKeys.Control) to int > 0
			if e.delta > 0
				.increaseFontHandler(nil, nil)
			else if e.delta < 0
				.decreaseFontHandler(nil, nil)
			e.handled = true

	def defaultCanExecute(sender, e as CanExecuteRoutedEventArgs)
		e.canExecute = true
		e.handled = true

	def saveCanExecute(sender, e as CanExecuteRoutedEventArgs)
		e.canExecute = _isModified
		e.handled = true

	def undoCanExecute(sender, e as CanExecuteRoutedEventArgs)
		e.canExecute = _textBox.canUndo
		e.handled = true

	def redoCanExecute(sender, e as CanExecuteRoutedEventArgs)
		e.canExecute = _textBox.canRedo
		e.handled = true

	def newExecuted(sender, e as ExecutedRoutedEventArgs)
		res = .promptUserToSave
		if res == MessageBoxResult.Cancel
			return
		else
			_textBox.text = ''
			.path = nil
			.revive
			.status = 'New.'
		e.handled = true

	def openExecuted(sender, e as ExecutedRoutedEventArgs)
		res = .promptUserToSave
		if res == MessageBoxResult.Cancel
			return
		else if res == MessageBoxResult.Yes
			.saveFile(_path, _textBox.text)
		else
			dialog = OpenFileDialog(filter=_filters)
			if dialog.showDialog
				.path = dialog.fileName to !
				_textBox.text = File.readAllText(_path)
				.revive
				.status = 'Opened.'
		e.handled = true

	def saveExecuted(sender, e as ExecutedRoutedEventArgs)
		# If a path is set for the currently edited file then save the text,
		# else prompt the user for a filename then save to that location
		if _path and _isModified
			.saveFile(_path, _textBox.text)
		else if _isModified
			newPath = .promptSaveFileName
			if newPath
				.path = newPath
				.saveFile(_path, _textBox.text)
		e.handled = true

	def saveAsExecuted(sender, e as ExecutedRoutedEventArgs)
		newPath = .promptSaveFileName
		if newPath
			.path = newPath
			.saveFile(_path, _textBox.text)
	
	def printExecuted(sender, e as ExecutedRoutedEventArgs)
		dialog = PrintDialog()
		if dialog.showDialog
			.status = 'Printing...'
			flowDoc = FlowDocument(columnWidth=700.0f, fontFamily=FontFamily('Consolas'))
			lines = _textBox.text.split(c'\n')
			for line in lines
				para = Paragraph(margin=Thickness(0))
				para.inlines.add(Run(line))
				flowDoc.blocks.add(para)
			paginator = (flowDoc to IDocumentPaginatorSource).documentPaginator
			dialog.printDocument(paginator, _path)
			.status = 'Printed.'
		
	def exitExecuted(sender, e as ExecutedRoutedEventArgs)
		.close

	def undoExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.undo
		e.handled = true

	def redoExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.redo
		e.handled = true

	def cutExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.cut
		e.handled = true

	def copyExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.copy
		e.handled = true

	def pasteExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.paste
		e.handled = true

	def deleteExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.selectedText = ''
		e.handled = true

	def selectAllExecuted(sender, e as ExecutedRoutedEventArgs)
		_textBox.selectAll
		e.handled = true
		
	def increaseFontExecuted(sender, e as ExecutedRoutedEventArgs)
		.increaseFontHandler(nil, nil)
		e.handled = true
		
	def decreaseFontExecuted(sender, e as ExecutedRoutedEventArgs)
		.decreaseFontHandler(nil, nil)
		e.handled = true
		
	def resetFontExecuted(sender, e as ExecutedRoutedEventArgs)
		.resetFontHandler(nil, nil)
		e.handled = true

	def promptUserToSave as MessageBoxResult
		"""
		Prompt's the user to commit unsaved changes.
		Returns true if a save was performed, else false
		"""
		if _isModified
			res = MessageBox.show('Do you want to save changes to [.fileName]?',
					'Save changes', MessageBoxButton.YesNoCancel)
			if res == MessageBoxResult.Yes
				# If the current file is a new file that has never been written to disk then 
				# prompt the user to provide a name and save it.
				if not _path
					newPath = .promptSaveFileName

					# If the user provides a filename/path then load this path, else if they cancel the
					# dialog box then return a 'No' result.
					if newPath
						.path = newPath
					else
						return MessageBoxResult.No

				if _path and _path.trim <> ''
					.saveFile(_path, _textBox.text)
			return res

		return MessageBoxResult.No

	def promptSaveFileName as String?
		dialog = SaveFileDialog(filter=_filters)
		dialog.fileName = .fileName
		if dialog.showDialog
			newPath = dialog.fileName to !
			return newPath
		return nil

	def saveFile(path as String?, content as String?)
		if path, File.writeAllText(path, content ? '')
		.revive
		.status = 'Saved.'

	def revive
		_isModified = false
		.status = ''


class Program

	def main is shared has STAThread
		Program().run

	def run
		app = Application()
		listen app.dispatcherUnhandledException, ref .unhandledExceptionHandler
		app.run(MainWindow())

	def unhandledExceptionHandler(sender, args as DispatcherUnhandledExceptionEventArgs)
		msg = 'Exception: ' + args.exception.message
		MessageBox.show(msg, 'Unhandled Exception', MessageBoxButton.OK)
		args.handled = true

Posted on 2012-10-21 19:35 with python in 0.422 sec.