Sunday, April 17, 2011

Mail with GUI

One of the examples that is used to demonstrate Factor to new users is sending an e-mail. Using the smtp vocabulary, all you need to send an email is:

( scratchpad ) <email>
                   { "" } >>to
                   "Up for lunch?" >>subject
                   "At Tracy's." >>body
Note: you might need to configure your SMTP settings before this works.


That's nice, but wouldn't it be neat if we could use the UI to build a compose window that can send emails in a more graphical manner? Perhaps something like this:

We're going to use words from several vocabularies:

USING: accessors arrays colors.constants kernel smtp ui
ui.commands ui.gadgets ui.gadgets.borders ui.gadgets.buttons
ui.gadgets.editors ui.gadgets.labels ui.gadgets.scrollers
ui.gadgets.tracks ui.pens.solid ;

We define a type of track layout that holds editor gadgets for each of the fields we need to receive input for. We will set the "To:" field to have focus first when the window is displayed.

TUPLE: mail-gadget < track to subject body ;

M: mail-gadget focusable-child* to>> ;

We build the UI using words that layout each of the main features:

: <to> ( mail -- gadget )
    to>> "To:" label-on-left ;

: <subject> ( mail -- gadget )
    subject>> "Subject:" label-on-left ;

: <body> ( mail -- gadget )
    body>> <scroller> COLOR: gray <solid> >>boundary ;

Using the command framework, we can create commands for "Send" and "Cancel" and configure it so a toolbar could be created automatically.

: com-send ( mail -- )
        over to>> editor-string 1array >>to
        over subject>> editor-string >>subject
        over body>> editor-string >>body
    send-email close-window ;

: com-cancel ( mail -- )
    close-window ;

mail-gadget "toolbar" f {
    { f com-send }
    { f com-cancel }
} define-command-map

Finally, we make a word that creates our mail gadget:

: <mail-gadget> ( -- gadget )
    vertical mail-gadget new-track
        1 >>fill
        { 10 10 } >>gap

        <editor> >>to
        <editor> >>subject
            10 >>min-rows
            60 >>min-cols

        dup <to>      f track-add
        dup <subject> f track-add
        dup <body>    1 track-add
        dup <toolbar> f track-add ;

And a simple word to open the gadget in a new "compose" window (with a 5-pixel border for aesthetic reasons):

: open-compose-window ( -- )
        { 5 5 } <border> { 1 1 } >>fill
    "Compose" open-window ;


You can even print the mail gadget out in the Listener to see how it looks. Note: it's fully functional, so be careful clicking those buttons!

Some things we could do to improve this simple example:

The code for this is on my Github.