Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Lisp: Слезы радости, часть 9

Оригинал: "Lisp: Tears of Joy, Part 9"
Автор: Vivek Shangari
Дата публикации: March 1, 2012
Перевод: Н.Ромоданов
Дата публикации перевода: 29 октября 2012 г.
Первую статью серии читайте здесь.
Предыдущую статью серии читайте здесь.

Язык Lisp был оценен, как самый мощный язык программирования в мире. Но только очень небольшой процент самых элитных программистов пользуются им из-за его загадочного синтаксиса и его академической репутации. Это весьма печально, поскольку Lisp не так уж и трудно понять. Если вы хотите быть в числе избранных, то эти статьи для вас. Это девятая статья в серии, которая началась в июне 2011 года.

Бытует популярный миф о том, что Common Lisp не поддерживает графический интерфейс пользователя. Это не так. Системы Lisp поддерживают графический интерфейс начиная с конца 1970-х годов - задолго до того, как он появился на недорогих потребительских компьютерах. Компьютер Xerox Alto, разработанный в Xerox PARC в 1973 году, был первым компьютером, на котором использовалась метафора рабочего стола и графического интерфейса пользователя, управляемого мышью. Это был некоммерческий продукт. Позже, в 1981 году корпорацией Xerox был представлен компьютер Xerox Star. Это была первая коммерческая система, имеющая в своем составе графический интерфейс пользователя; он поставлялся с языками Lisp и SmallTalk, предназначенными для рынка исследований и разработки программного обеспечения (Apple Macintosh, выпущенный в 1984 году, был первым коммерчески успешным продуктом, в котором использовался многооконный графический пользовательский интерфейс).

Пользователи систем Windows и Macintosh иногда считают графический интерфейс языка Lisp грубоватым и немного незнакомым. В некоторых коммерческих средах языка Lisp предлагаются средства для сборки графического интерфейса - graphical interface builders, которые позволяют создавать виджеты с помощью выбора необходимого варианта щелчком мыши и перетягивания его в разрабатываемую программу, однако внешний вид этих виджетов зачастую "простоват". Как правило, в этих средах языка Lisp предоставляется набор программ-оболочек, созданных на базе графических процедур, поддерживаемых операционной системой, так что к этим оболочкам можно обращаться из вашей программы на языке Lisp.

Примеры использования инструментальных наборов LispWorks и CAPI

Пакет LispWorks является оболочкой IDE для ANSI Common Lisp. Он работает в Linux, Mac OS X, Windows и в других операционных системах. В нем используется интерфейс Common Application Programmer’s Interface (CAPI), который представляет собой библиотеку, предназначенную для реализации в приложениях переносимых оконных интерфейсов.

Интерфейс CAPI представляет собой концептуально простую CLOS-ориентированную модель интерфейсных элементов и средств взаимодействия с ними. В нем представлен стандартный набор подобных элементов и их поведения, а также есть возможность определять подобные элементы самостоятельно. CAPI в настоящее время работает под управлением системы X Window либо с GTK+, либо с Motif, а также в системах Microsoft Windows и Mac OS X. Использование CAPI с Motif считается устаревшим.

Давайте создадим несколько элементов графического интерфейса с использованием LispWorks и CAPI.

Меню

Вы можете создать меню для приложения с помощью класса menu. Давайте начнем с создания теста обратного вызова и функции hello, для которой мы должны создать и протестировать наш графический интерфейс.

(defun test-callback (data interface)
  (display-message "Data ~S in interface ~S"
                   data interface))
 
(defun hello (data interface)
  (declare (ignore data interface))
  (display-message "Hello World"))

В следующем коде создается интерфейс CAPI с меню Foo, в котором есть четыре элемента. При выборе любого из этих элементов отображаются аргументы выбранного элемента. В каждом элементе есть функция обратного вызова, указываемая с помощью ключевого слова :callback.

(make-instance 'menu
    :title "Foo"
    :items '("One" "Two" "Three" "Four")
    :callback 'test-callback)
 
(make-instance 'interface
    :menu-bar-items (list *))
 
(display *)

Подменю можно создать, просто указав меню вместо одного из элементов меню верхнего уровня.

(make-instance 'menu
    :title "Bar"
    :items '("One" "Two" "Three" "Four")
    :callback 'test-callback)
 
(make-instance 'menu
    :title "Baz"
    :items (list 1 2 * 4 5)
    :callback 'test-callback)
 
(contain *)

Здесь создается интерфейс, в котором есть меню с названием Baz, которое само состоит из пяти элементов. Третий элемент сам является еще одним меню Bar, которое состоит из четырех элементов. Еще раз, при выборе любого элемента меню возвращаются аргументы этого элемента. Подобный подход позволяет создавать вложенную структуру меню настолько глубоко, насколько это необходимо.

Класс menu-component позволяет сгруппировать связанные друг с другом элементы в меню вместе. В результате похожие элементы меню могут совместно пользоваться одними и теми же свойствами, например, обратными вызовами, но в меню они могут быть визуально отделены друг от друга. На самом деле, среди различных компонентов меню есть что выбрать.

Ниже приведен простой пример использования компонента меню. Здесь создается меню с названием Items, в котором есть четыре элемента. Menu 1 и Menu 2 являются обычными элементами меню, а элементы Item 1 и Item 2 созданы из компонента меню, и, следовательно, сгруппированы в меню так, как это показано на рис.1:

(setq component (make-instance 'menu-component
                     :items '("item 1" "item2")
                     :print-function 'string-capitalize
                     :callback 'test-callback))
 
(contain (make-instance 'menu
                        :title "Items"
                        :items
                        (list "menu 1" component "menu 2")
                        :print-function 'string-capitalize
                        :callback 'hello)
         :width 150
         :height 0)

Рис.1: Меню с компонентами меню

Радио-кнопки

Компоненты меню позволяют вам с помощью ключевого слова :interaction указать, как можно переключать меню — делать выбор сразу нескольких элементов или выбирать только один элемент. Это напоминает использование в меню в качестве элементов меню радио-кнопок или чек-боксов, которые популярны во многих приложениях, использующих графический пользовательский интерфейс. В следующем примере показано, как можно добавить в меню панель с радио-кнопками (см. рис.2):

(setq radio (make-instance 'menu-component
                :interaction :single-selection
                :items '("This" "That")
                :callback 'hello))
 
(setq commands (make-instance 'menu
                :title "Commands"
                :items
                 (list "Command 1" radio "Command 2")
                :callback 'test-callback))
 
(contain commands)

Рис.2: Радио-кнопки в меню

Элементы меню This и That являются радио-кнопками, причем в каждый конкретный момент можно выбрать только один из них. Остальные элементы обычные, как и в предыдущем примере. Обратите внимание, что CAPI автоматически группирует элементы, которые являются частью компонента меню, так что в меню они отделены от других элементов.

Меню с пометками - чек-боксы

Приведенный выше пример также иллюстрирует использование в меню более чем одного обратного вызова, что, конечно, является обычным делом при разработке реальных приложений. При выборе одной из радио-кнопок на экране отображается одно сообщение и в качестве аргументов обратного вызова возвращается либо Command1, либо Command2.

Выбираемые элементы меню можно создавать с указанием :multiple-selection для ключевого слова :interaction так, как это показано на рисунке ниже (см. рис.3):

(setq letters (make-instance 'menu-component
                  :interaction :multiple-selection
                  :items (list "Alpha" "Beta")))
 
(contain (make-instance 'menu
                  :title "Greek"
                  :items (list letters)
                  :callback 'test-callback))

Рис.3: Меню с несколькими помечаемыми элементами

Обратите внимание, что элементы компонента меню наследуют обратный вызов родительского элемента, что избавляет от необходимости указывать отдельный обратный вызов для каждого элемента или компонента в меню. В меню или в компоненте меню вы можете указать альтернативные варианты, которые будут вызваны для элемента основного меню с помощью горячих клавиш.

Класс menu-item позволяет вам создавать отдельные пункты меню, которые можно передать в компоненты меню или в меню с указанием ключевого слова :items. С помощью этого класса вы можете различным элементам меню назначать различные функции обратного вызова. Помните, что каждый экземпляр этого класса не должен использоваться одновременно белее, чем в одном месте.

(setq test (make-instance 'menu-item
                            :title "Test"
                            :callback 'test-callback))
 
(setq hello (make-instance 'menu-item
                            :title "Hello"
                            :callback 'hello))
 
(setq group (make-instance 'menu-component
                            :items (list test hello)))
 
(contain group)

Рис.4: Отдельное меню

С помощью комбинирования элементов меню, компонентов меню и меню нижнего уровня можно создать иерархическую структуру. Меню в коде, приведенном ниже, состоит из пяти элементов, одним из которых является само меню (с тремя элементами меню), а остальные являются компонентами меню и элементами меню. Элементы в меню наследуют значения своих родительских элементов, что позволяет похожим элементам при любой возможности пользоваться похожими свойствами.

(defun menu-item-name (data)
  (format nil "Menu Item ~D" data))
 
(defun submenu-item-name (data)
  (format nil "Submenu Item ~D" data))
 
(contain
 (make-instance
  'menu
  :items
  (list
   (make-instance 'menu-component
                  :items '(1 2)
                  :print-function 'menu-item-name
                  )
 
   (make-instance 'menu-component
                  :items
                  (list 3
                        (make-instance
 
                         'menu
                         :title "Submenu"
                         :items '(1 2 3)
                         :print-function
                         'submenu-item-name))
                  :print-function 'menu-item-name)
 
   (make-instance 'menu-item
                  :data 42))
  :print-function 'menu-item-name))

Рис.5: Иерархия меню

В LispWorks вместо того, чтобы создавать элементы пользовательского интерфейса непосредственно в программе, также предлагается инструмент Interface Builder, который позволяет строить графический пользовательский интерфейс в приложениях на Lisp-е. Вы можете в вашем приложении создать и протестировать каждое окно или диалог - Interface Builder создаст необходимый исходный код для окон, которые у вас есть; все, что вам нужно сделать для того, чтобы можно было использовать ваш собственный исходный код, это добавить обратные вызовы в сгенерированный код.

Продолжение следует...