- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF128:Clutter
Материал из Linuxformat.
- Python Реальные проекты для развития ваших навыков
Содержание |
Clutter: Пишем датчик скорости
Python |
---|
Python для профессионалов |
---|
Python + PyGame |
---|
Python + Web |
---|
Python + Clutter |
---|
- Используя код из Clutter, Ник Вейч уведет вас от командной строки в новую реальность полноцветных графических приложений.
Внашей серии уроков Python мы уже написали множество web-диковинок, но лишь изредка касались создания графического интерфейса (GUI). Одна из причин этого в том, что в большинстве случаев код GUI очень быстро разрастается, и обычная отрисовка на экране панели с несколькими кнопками способна съесть всю площадь статьи.
Сделаем небольшой перерыв в недружелюбии к пользователю, поскольку в следующие месяцы мы будем создавать приложения с использованием библиотеки PyClutter. Если вы не знакомы с Clutter [англ. «захламлять»], то обратитесь к врезке в конце.
На первом уроке мы создадим небольшую, но полезную утилиту, чтобы разобраться в работе Clutter и PyClutter. Поскольку Clutter используется пока не очень широко, то документации и примеров весьма не хватает; будем надеяться, что приведенный здесь код даст вам представление, как можно реально применять Clutter в приложениях Python.
Задача данного урока – создать программку, отображающую скорость вашего текущего интернет-соединения. Ну да, таких мониторов хватает и без нас, но этот будет наш собственный, и всего из 70 строк простого кода.
Чтобы разобраться в Clutter, начнем с терминологии. В отличие от других GUI-инструментариев, обычно определяющих объекты типа панелей или окон, Clutter рассматривает визуальную область как «сцену» [stage]. Продолжая аналогию – появляющиеся на ней (точнее, в ней, но это как-то нелепо звучит) объекты называют ся актерами [actor]. В процессе кодирования все станет понятнее, и названия уже не будут казаться странными. Особенность актеров в том, что они обладают большим количеством свойств, чем обычные виджеты, потому что на самом деле существуют в 3D-окружении, а не в 2D.
Зачем нужен Clutter?
Clutter – это лицензированная по GPL графическая и GUI-библиотека, изначально разработанная командой OpenedHand. Позднее ее продали Intel, которая продолжила дальнейшую разработку и развитие.
Самое замечательное в Clutter – простота, скорость и мощные методы создания 3D-и 2D-графики на разных платформах. За кулисами в основном используется OpenGL, но, применяя библиотеку Clutter, разработчики приобретают преимущества быстрого, эффективного и дружелюбного способа создания графических приложений без углубления в технические аспекты OpenGL-библиотек.
Clutter также формирует неотъемлемую часть Moblin, последнее слово в создании легковесной, но мощной графической версии Linux для мобильных устройств. Moblin в основном предназначена для устройств на базе Intel Atom, но работает и на других машинах.
Весь мир — сцена
Прямо по Шекспиру. Но довольно гиперболтовни – все прояснится, когда мы начнем создавать код. Откройте свое обычное окружение Python (для меня это Bash, но вы можете использовать нечто более симпатичное), и создайте первый скрипт Clutter...
>>> import clutter >>> stage = clutter.Stage() >>> stage.set_size(500,300) >>> red=clutter.Color(255,0,0,255) >>> black=clutter.Color(0,0,0,255) >>> stage.set_color(black) >>> stage.show_all()
Налюбовавшись, щелкните по кнопке закрытия окна. Я понимаю, что пока не видно ничего замечательного, но потенциал здесь есть! Рассмотрим, что же произошло. Первая строка, очевидно, загружает модуль Clutter. Далее Clutter сам открывает несколько модулей – это служебные механизмы, в конечном итоге подключающие системные библиотеки для вывода графики на экране. Следующим шагом мы создаем объект-сцену. Сцена – это вроде рамки видоискателя: область, в пределах которой действуют ваши актеры.
Настройка атрибутов сводится к простому вызову методов класса stage, в нашем случае это размер и цвет. Параметры для set_szie() – длины вдоль x и y, а цвет берется из объекта clutter.Color (требующего значения RGB и альфа-канала). Как и в других инструментариях, прежде чем объект появится на экране, его необходимо сделать видимым – этим и занимается по следняя строка.
Но что же с актерами – объектами, которые мы хотим показать на экране? Давайте добавим несколько текстовых надписей:
>>> a=clutter.Text() >>> a.set_font_name(“Sans 30”) >>> a.set_colour(red) >>> a.set_text (“Hello World!”) >>> a.set_position(130,100) >>> stage.add(a)
Мы добавили текстовый объект, нашего первого актера. Надеюсь, вам ясно, что делают эти методы: выбирается шрифт, цвет, задается текстовая строка и ее расположение. Последний вызов в коде примера добавляет актера на сцену, чтобы вы смогли его увидеть. А теперь с этим можно поиграть – попробуйте установить другую позицию или добавить цвета.
Как я отметил ранее, документация к PyClutter скудна, но мы можем утешиться тем фактом, что Python – сам себе анализ. Попробуйте сейчас ввести dir(a), чтобы увидеть доступные методы и атрибуты этого объекта.
Следующим шагом мы создадим работающий скрипт, но остался еще один момент: чтобы магия Clutter действовала, нужно передать ему управление приложением посредством функции clutter.main(), предварительно предусмотрев выход из программы. В ситуациях, подобных нашей, Python перехватывает прерывание Ctrl+C, и пока у нас нет шанса выйти: надо создать некоторые события клавиатуры.
Когда окно сцены активно, Clutter воспринимает сигналы о нажатии клавиш. Все, что нам осталось – написать функцию, которая будет обрабатывать эти события и при нажатии некой клавиши выходить из основного цикла. Кроме того, можно назначить действия другим клавишам: например, для смена цвета сцены.
>>> def parseKeyPress(self, event): ... if event.keyval == clutter.keysyms.q: ... clutter.main_quit() ... elif event.keyval == clutter.keysyms.r: ... self.set_color(red) ... >>> stage.connect(‘key-press-event’, parseKeyPress) >>> clutter.main()
Библиотека Clutter, а следовательно, и модули Python, использующие ее, недавно обновились до версии 1.0. Для обновлений обычна некоторая несовместимость между старой и новой версиями, но в данном случае в коде версий до и после 0.9 отличия фундаментальные. Модуль PyClutter и библиотека Clutter должны быть доступны в репозитории вашего дистрибутива, но после ее установки убедитесь, что вы получили версию 0.9 (лучше 1.0) или выше; в противном случае гарантирую, что код данного учебника работать не будет. Кто подумал «фи», пусть напишет учебник, а затем поинтересуется обновлениями библиотеки…
При запуске в интерактивной оболочке Python функция quit не приведет к выходу из самого Python, и даже не уничтожит приложение, а просто передаст управление обратно оболочке. Однако при запуске скрипта вызов метода clutter.main_quit() на самом деле завершит приложение – ну, по крайней мере, его часть, связанную с Clutter.
Время что-то отследить
Итак, интерфейс у нас готов, но как создать великолепный монитор трафика? Сперва определим, где взять текущую скорость. В таких случаях я всегда обращаюсь к моему старому другу proc. Да, псевдо-файловая система /proc – это кладезь всевозможных сведений о работающей Linux-машине; из всей беспорядочной мешанины файлов здесь нам нужен лишь /proc/net/dev. Это список всех сетевых устройств, и его просмотр даст нам статистику входящих и исходящих байтов, пакетов, пропущенных пакетов, ошибок и т. д. Нам сейчас интересны только полученные и отправленные байты. Я знаю, что указывается их количество, а нам необходи ма скорость, но узрите мощь proc – просто откройте файл вновь, и числа изменятся, как по волшебству. Надеюсь, вы поспеваете за мной и простая арифметика для нас не препятствие. Если открывать файл каждую секунду и вычитать старое число из нового, то вот вам и ответ.
Короче, надо создать небольшую функцию, которая будет читать файл, выделять необходимую информацию и вычислять разность. Перед ее завершением будем сохранять старое число, чтобы вычитать его в следующий раз. Ниже показано, как примерно должна выглядеть функция:
devfile=open(‘/proc/net/dev’,’r’) for line in devfile.readlines(): line=line.strip() if (line[:4] == ‘eth0’): line=line[5:].split() print line[0], line[8]
Авось, вы разберетесь в ее коде и без блок-схемы. Мы читаем файл и проходим по строкам в поиске той, что начинается с eth0; перед сравнением вырезаются пробелы, которыми вывод дополняется для выравнивания таблицы. Обнаружив нужную строку, мы удаляем имя интерфейса и разбиваем ее, чтобы каждое значение стало элементом списка. Количества входящих и исходящих байтов будут находиться в 0‑й и 8‑й его позициях. Пока мы просто печатаем их – можете набрать наш код и посмотреть, что выведется. Необходимо добавить лишь преобразование строки в целое и его сохранение, и можно начинать отслеживать происходящее.
Поможет математика
Любители вникать в практические детали могут спросить: а как мы учтем время, занятое выполнением кода? Хотите засечь время выполнения этого кода – вперед: в моей системе на него требуется 0.0001 секунд. Если интересно, то полное приложение командной строки будет выглядеть так:
Отслеживание версий может превратиться в кошмар, но большинство модулей хранит свои версии в <имя_модуля>.__version__ . Это может быть полезно не только для вас, но и для вашего приложения, которое может проверять совместимость версий перед тем, как сделать нечто хитроумное.
import time lasttime=1 lastin=0 lastout=0 def getspeed(): x=open(‘/proc/net/dev’,’r’) for line in x.readlines(): line=line.strip() if (line[:4] == ‘eth0’): line=line[5:].split() bin=int(line[0]) bout=int(line[8]) return (bin, bout) while True : z= getspeed() timedelta=time.time()-lasttime lasttime=time.time() sin=(float(z[0]-lastin))/(1024*timedelta) sout=(float(z[1]-lastout))/(1024*timedelta) print sin, sout lastin=z[0] lastout=z[1] time.sleep(5)
Сюда вставлена функция таймера для более точного расчета скорости, но, принимая во внимание, что речь идет о паре миллисекунд, большой разницы не ждите. Однако функция пригодится, если мы пожелаем изменить период времени где-нибудь в приложении.
Теперь включим эту функциональность в приложение Clutter. Мы могли бы просто воткнуть цикл в конец нашей программы и вообще не вызывать главный цикл Clutter. Обновлять актера при необходимости все равно можно, но это будет Плохой Идеей. Более элегантный способ – вернуть актеру свободу и автономность и создать анимационную шкалу времени для управления его текстом.
Шкала времени более подробно описана во врезке; а если в двух словах, то это просто таймер, ведущий счет до некоторого значения и затем выдающий программный эквивалент зуммера – сигнал. Сигнал можно перехватить и передать функции обратного вызова; кроме того, можно передать дополнительные параметры. Для наших целей мы можем заставить таймер вызывать функцию, запрашивающую скорость сети и обновляющую оба наших актера.
Шкала времени и сама по себе объект, но, создавая связь между ней и функцией обратного вызова, мы можем передать последней и наши объекты-актеры, чтобы она могла изменять их непосредственно. Отметим, что если вы желаете более сложного поведения, то не запрещено использовать также и другие таймеры – при желании можно настроить один для ежесекундного изменения цвета объектов, и его не нужно увязывать с уже созданной шкалой времени. Шкалы времени могут использоваться как потоки в многопоточных приложениях – они не столь гибкие, но ими легче управлять, и они проще при работе с анимированными объектами, потому что можно отделить анимацию объектов от других связанных с ними взаимодействий.
import clutter import time lasttime=1 lastbin=0 lastbout=0 black =clutter.Color(0,0,0,255) red = clutter.Color(255, 0, 0, 255) green =clutter.Color(0,255,0,255) blue =clutter.Color(0,0,255,255) def updatespeed(t, a, b): global lasttime, lastbin, lastbout f=open(‘/proc/net/dev’,’r’) for line in f.readlines(): line=line.strip() if (line[:4] == ‘eth0’): line=line[5:].split() bin=int(line[0]) bout=int(line[8]) timedelta=time.time()-lasttime lasttime=time.time() speedin=round((bin-lastbin)/(1024*timedelta), 2) speedout=round((bout-lastbout)/(1024*timedelta), 2) lastbin, lastbout = bin, bout a.set_text(str(speedin)+’KB/s’) xx, yy=a.get_size() a.set_position(int((300-xx)/2),int((100-yy)/2) ) b.set_text(str(speedout)+’KB/s’) xx, yy=b.get_size() b.set_position(int((300-xx)/2),int((100-yy)/2)+100 ) def parseKeyPress(self, event): # Опрашиваем клавиату ру # Вызывается объек том сцены if event.keyval == clutter.keysyms.q: #Вы ходим из тес та, ес ли поль зователь на жал “q” clutter.main_quit() elif event.keyval == clutter.keysyms.r: #де лаем объект красным при на жатии “r” self.set_color(red) elif event.keyval == clutter.keysyms.g: # де лаем объект зе леным при на жатии “g” self.set_color(green) elif event.keyval == clutter.keysyms.b: # де лаем объект синим при на жатии “b” self.set_color(blue) elif event.keyval == clutter.keysyms.Up: #стрелка вверх = де лаем объект черным self.set_color(black) print ‘event processed’, event.keyval stage = clutter.Stage() stage.set_size(300,200) stage.set_color(blue) stage.connect(‘key-press-event’, parseKeyPress) intext=clutter.Text() intext.set_font_name(“Sans 30”) intext.set_color(green) stage.add(intext) outtext=clutter.Text() outtext.set_font_name(“Sans 30”) outtext.set_color(red) stage.add(outtext) stage.show_all() t=clutter.Timeline() t.set_duration(5000) t.set_loop(True) t.connect(‘completed’, updatespeed, intext, outtext) t.start() clutter.main()
Здесь мы собрали воедино все элементы, изученные на этом уроке. Мы создали сцену, заселили ее актерами, а за тем воспользовались объектами Clutter типа «шкала времени», чтобы все обновлялось самостоятельно и согласно нашим капризам. Но мы лишь слегка коснулись графических возможностей Clutter. Мы даже еще не затронули поведение и анимацию, не говоря уж об эффектах альфа-канала. Но верьте: они появятся вгрядущих проектах.
Все о таймерах
Библиотека Clutter использует объекты, называемые шкалой времени [timeline] для выполнения практически всего, что необходимо делать при работе приложения. Шкала времени – это пульс вашего скрипта, который гарантирует, что все по крайней мере пытается функционировать совместно.
Шкала времени широко используется в Clutter для управления анимацией и эффектами, но ее также можно применять как самостоятельное прерывание для периодического вызова подпрограмм. Это достигается реакцией на сигналы для таких событий, как started, next-frame, completed и т. д. Каждый из этих сигналов можно связать с функцией обратного вызова для управления чем-то еще.
Вот короткий пример, который можно ввести в оболочке Python:
>>> import clutter >>> t=clutter.Timeline() >>> t.set_duration(2000) >>> t.set_loop(True) >>> def ping(caller): ... print caller ... >>> t.connect(‘completed’,ping) 9L >>> t.start() >>> <clutter.Timeline object at 0xb779639c (ClutterTimeline at 0x95b9860)>
Надеюсь, методы объекта временной шкалы достаточно понятны. Длительность задается в миллисекундах. Затем шкала зацикливается. Здесь мы создали простую функцию с именем ping, которая просто печатает передаваемый ей параметр. Затем мы связали подаваемый сигнал completed с функцией ping и запустили шкалу времени. Теперь без всякого дополнительного воздействия функция ping будет вызываться каждые две секунды позавершении отрезка временной шкалы, пока вы не закроете окружение Python.