Notepad Sample
Blind Watch Maker 1
Download
Find Words
forth
Fractal Benchmark
Genetic Algorithm
Hex Dump
Notepad
Point
Shapes
Simple English Parser
Sizes
TPK
Word Count
"""
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\Reference Assemblies\Microsoft\Framework\v3.0'
@ref 'PresentationFramework'
@ref 'PresentationCore'
@ref 'WindowsBase'

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