Дружим с мышкой
Добрый день! 
В этом топике я хотел бы рассказать о перехвате событий мыши в приложениях на pygtk.
Редко когда приходится обращаться к этой возможности, но её наличие в тех или иных случаях идёт на руку. В своё время я занимался проектом, в котором она бы не помешала. Но на тот момент я не нашёл достаточной документации, да и, честно говоря, не могу найти и по сей день. Пришлось хитро изворачиваться через drag'n'drop, но давайте не будем о плохом.
Возможно я плохо искал? Если вы можете посоветовать какой-либо мануал по pygtk кроме этого буду очень признателен!
Я не хотел бы начинать с основ «стряпанья» интерфейсов в glade, поэтому грех было не закинуть сюда ссылки на этот и этот топики.
Это, как вы уже догадались, первый способ. Мы просто указываем какие дополнительные события стоит обрабатывать виджету и добавляем обработчики. Пока мы просто установим нужные галочки в glade. Я решил для примера написать приложение, где можно будет двигать картинками по макету.
Сперва стоит набросать в glade что-нибудь. Сразу скажу, что пользуюсь glade-2.12, вместо glade3, просто первый мне кажется более удобным и минималистичным. Поэтому мои скрины будут чуточку отличатся от тех, что вы привыкли видеть. Но различия минимальны. В конце концов я набросал что-то типа этого:

Теперь нужно установить те самые галочки. Выберем этот GtkLayout как виджет с дополнительным обработчиком. В так называемых «Общих» свойствах виджета есть «События» — мистическая строка из нулей и единиц(glade-2.12, в glade3 просто в свойствах есть раздел Events, как раз с галочками), это то что нам и нужно. Жмём на кнопку:

И выбираем нужные нам обработчики. Мне понадобятся только три, они отмечены на рисунке.
Теперь во вкладке сигналы создадим обработчики на события motion_notify_event, button_press_event и button_release_event. Как я их обозвал будет видно из листинга. Собственно вот и он:

Как ни странно оно работает. Но это, однако, не единственный способ отловить мышку в окне приложения. Готовый скрипт со всем прилагающимся располагается здесь
Можно сократить работу на несколько шагов, используя уже готовый виджет со всеми этими событиями всключенными по стандарту, коим является EventBox. В списке виджетов его найти не составит труда. Вообще практически все шаги будут аналогичны предыдущим, за исключением включения событий. Поэтому я не буду их описывать во всех красках. В качестве примера я написал небольшую рисовалку красных линий в окне. Вот то, что я набросал в glade:

А вот прилагающийся листинг:

Скрипт со всем прилагающимся можно взять тут.
Ну и на сладкое, как всё это сделать непосредственно кодом, без прибегания к помощи glade? Очень просто. Настолько просто, что я бы даже отказался бы от помощи glade в этом вопросе. Лишь попробуйте ввести это в интерпретаторе:

В этом топике я хотел бы рассказать о перехвате событий мыши в приложениях на pygtk.
Редко когда приходится обращаться к этой возможности, но её наличие в тех или иных случаях идёт на руку. В своё время я занимался проектом, в котором она бы не помешала. Но на тот момент я не нашёл достаточной документации, да и, честно говоря, не могу найти и по сей день. Пришлось хитро изворачиваться через drag'n'drop, но давайте не будем о плохом.
Возможно я плохо искал? Если вы можете посоветовать какой-либо мануал по pygtk кроме этого буду очень признателен!
Я не хотел бы начинать с основ «стряпанья» интерфейсов в glade, поэтому грех было не закинуть сюда ссылки на этот и этот топики.
Привязка дополнительных событий к виджету
Это, как вы уже догадались, первый способ. Мы просто указываем какие дополнительные события стоит обрабатывать виджету и добавляем обработчики. Пока мы просто установим нужные галочки в glade. Я решил для примера написать приложение, где можно будет двигать картинками по макету.
Сперва стоит набросать в glade что-нибудь. Сразу скажу, что пользуюсь glade-2.12, вместо glade3, просто первый мне кажется более удобным и минималистичным. Поэтому мои скрины будут чуточку отличатся от тех, что вы привыкли видеть. Но различия минимальны. В конце концов я набросал что-то типа этого:

Теперь нужно установить те самые галочки. Выберем этот GtkLayout как виджет с дополнительным обработчиком. В так называемых «Общих» свойствах виджета есть «События» — мистическая строка из нулей и единиц(glade-2.12, в glade3 просто в свойствах есть раздел Events, как раз с галочками), это то что нам и нужно. Жмём на кнопку:

И выбираем нужные нам обработчики. Мне понадобятся только три, они отмечены на рисунке.
Теперь во вкладке сигналы создадим обработчики на события motion_notify_event, button_press_event и button_release_event. Как я их обозвал будет видно из листинга. Собственно вот и он:
-
- #!/usr/bin/env python
- #-*- coding:utf-8 -*-
-
- import pygtk
- pygtk.require('2.0')
-
- import gtk
- import gtk.glade
-
-
- class Application(object):
-
-
- def __init__(self):
-
- #
-
- self.gladefile = 'main.glade'
-
- self.widgets_tree = gtk.glade.XML(self.gladefile)
-
- # Эти самые обработчики.
- main = {
- 'layout_motion': self.motion,
- 'layout_mouse_press': self.mouse_press,
- 'layout_mouse_release': self.mouse_release }
-
- self.widgets_tree.signal_autoconnect(main)
-
- self.wget = self.widgets_tree.get_widget
-
- self.window = self.wget('window')
-
- assert self.window
-
- self.window.connect('destroy', self.close_app)
-
- #
-
- # Переменная, хранящая ссылку на вижет, который был выбран.
- self.selected_child = None
- # Координаты клика относительно этого виджета.
- self.catched_x = 0
- self.catched_y = 0
-
-
- def close_app(self, widget):
-
- self.window.destroy()
- gtk.main_quit()
-
-
- def motion(self, widget, event):
-
- # Эта функция срабатывает, если возить мышкой с нажатой кнопкой по окну.
- if self.selected_child:
- # Ловим координаты выбранного виджета.
- this = self.selected_child
- catched_x = self.catched_x
- catched_y = self.catched_y
-
- # Меняем их в соответствии с положением мышки.
- widget.child_set_property(this, 'x', event.x - catched_x)
- widget.child_set_property(this, 'y', event.y - catched_y)
-
-
- def mouse_press(self, widget, event):
-
- # Эта функция сработает, если мы будем кликать где-ниубдь на окне.
- # Нам нужно определить, на каком виджете оказалась мышь, когда мы кликнули.
- for w in widget.get_children():
- # Получаем необходимые координаты, а так же длинну и высоту виджета.
- this_x = widget.child_get_property(w, 'x')
- this_y = widget.child_get_property(w, 'y')
- this_width, this_height = w.get_size_request()
- # Простая проверка.
- if this_x < event.x < this_width + this_x:
- if this_y < event.y < this_height + this_y:
- self.selected_child = w
- self.catched_x = event.x - this_x
- self.catched_y = event.y - this_y
- break
-
-
- def mouse_release(self, widget, event):
-
- # Тут, думаю, всё очевидно.
- self.selected_child = None
-
-
-
- if __name__ == '__main__':
- Application()
- gtk.main()
-
-
-
Отлично. Запускаем свежеприготовленный скрипт: 
Как ни странно оно работает. Но это, однако, не единственный способ отловить мышку в окне приложения. Готовый скрипт со всем прилагающимся располагается здесь
EventBox
Можно сократить работу на несколько шагов, используя уже готовый виджет со всеми этими событиями всключенными по стандарту, коим является EventBox. В списке виджетов его найти не составит труда. Вообще практически все шаги будут аналогичны предыдущим, за исключением включения событий. Поэтому я не буду их описывать во всех красках. В качестве примера я написал небольшую рисовалку красных линий в окне. Вот то, что я набросал в glade:

А вот прилагающийся листинг:
-
- #!/usr/bin/env python
- #-*- coding:utf-8 -*-
-
- import pygtk
- pygtk.require('2.0')
-
- import gtk
- import gtk.glade
-
-
- class Application(object):
-
-
- def __init__(self):
-
- #
-
- self.gladefile = 'main.glade'
-
- self.widgets_tree = gtk.glade.XML(self.gladefile)
-
- main = {
- 'layout_press_event': self.layout_press,
- 'layout_release_event': self.layout_release }
-
- self.widgets_tree.signal_autoconnect(main)
-
- self.wget = self.widgets_tree.get_widget
-
- self.window = self.wget('window')
-
- assert self.window
-
- self.window.connect('destroy', self.close_app)
-
- #
-
- self.drawing = self.wget('drawing')
-
-
- def close_app(self, widget):
-
- self.window.destroy()
- gtk.main_quit()
-
-
- def layout_press(self, wigdet, event):
-
- # Запоминаем координаты, где мы кликнули мышкой.
- self.xy0 = (event.x, event.y)
-
-
- def layout_release(self, widget, event):
-
- # Запоминаем координаты, где мышь была отпущенна и вызываем draw().
- self.xy1 = (event.x, event.y)
- self.draw()
-
-
- def draw(self):
-
- # По поводу кода ниже - рекомендую познакомится со ссылкой:
- # doc.crossplatform.ru/python/pygtk/2.4/ch-DrawingArea.html#sec-GraphicsContext
- drawable = self.drawing.window
- colormap = self.drawing.get_colormap()
- assert drawable
-
- this_color = gtk.gdk.Color(red=0xffff, green=0x0, blue=0x0)
- this_foreground = colormap.alloc_color(this_color)
- gc = drawable.new_gc( foreground=this_foreground,
- background=this_foreground,
- line_width=2,
- line_style=gtk.gdk.LINE_SOLID,
- join_style=gtk.gdk.JOIN_MITER,
- cap_style=gtk.gdk.CAP_BUTT,
- fill=gtk.gdk.SOLID,
- function=gtk.gdk.COPY )
-
- x0, y0 = [int(x) for x in self.xy0]
- x1, y1 = [int(x) for x in self.xy1]
-
- drawable.draw_line(gc, x0, y0, x1, y1)
-
-
-
- if __name__ == '__main__':
- Application()
- gtk.main()
-
-
-
Всё достаточно просто, не так ли? Результат работы будет выглядеть так: 
Скрипт со всем прилагающимся можно взять тут.
А как реализовать это без glade?
Ну и на сладкое, как всё это сделать непосредственно кодом, без прибегания к помощи glade? Очень просто. Настолько просто, что я бы даже отказался бы от помощи glade в этом вопросе. Лишь попробуйте ввести это в интерпретаторе:
-
- import gtk
- window = gtk.Window(gtk.WINDOW_TOPLEVEL)
- layout = gtk.Layout()
- # В этой строке и таится весь сакральный смысл.
- layout.set_events(gtk.gdk.BUTTON1_MASK|gtk.gdk.BUTTON3_MOTION_MASK)
- # Спешу заметить, что BUTTON3 - это вторая кнопка мыши, а не третья, как могло бы показаться.
- window.add(layout)
-
- # пишем обработчик для всех событий. Пусть просто выводит в консоль что произошло.
- def event(widget, event):
- <div style="font: normal normal 1em/1.2em monospace; margin:0; padding:0; backgrou
комментарии (10)