Дружим с мышкой

Добрый день!

В этом топике я хотел бы рассказать о перехвате событий мыши в приложениях на 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. Как я их обозвал будет видно из листинга. Собственно вот и он:
  1.  
  2. #!/usr/bin/env python
  3. #-*- coding:utf-8 -*-
  4.  
  5. import pygtk
  6. pygtk.require('2.0')
  7.  
  8. import gtk
  9. import gtk.glade
  10.    
  11.    
  12. class Application(object):
  13.  
  14.  
  15.     def __init__(self):
  16.    
  17.         #
  18.        
  19.         self.gladefile = 'main.glade'
  20.        
  21.         self.widgets_tree = gtk.glade.XML(self.gladefile)
  22.        
  23.         # Эти самые обработчики.
  24.         main = {
  25.             'layout_motion': self.motion,
  26.             'layout_mouse_press': self.mouse_press,
  27.             'layout_mouse_release': self.mouse_release }
  28.                        
  29.         self.widgets_tree.signal_autoconnect(main)
  30.        
  31.         self.wget = self.widgets_tree.get_widget
  32.        
  33.         self.window = self.wget('window')
  34.        
  35.         assert self.window
  36.        
  37.         self.window.connect('destroy', self.close_app)
  38.        
  39.         #
  40.        
  41.         # Переменная, хранящая ссылку на вижет, который был выбран.
  42.         self.selected_child = None
  43.         # Координаты клика относительно этого виджета.
  44.         self.catched_x = 0
  45.         self.catched_y = 0
  46.        
  47.                
  48.     def close_app(self, widget):
  49.        
  50.         self.window.destroy()
  51.         gtk.main_quit()
  52.    
  53.    
  54.     def motion(self, widget, event):
  55.        
  56.         # Эта функция срабатывает, если возить мышкой с нажатой кнопкой по окну.
  57.         if self.selected_child:
  58.             # Ловим координаты выбранного виджета.
  59.             this = self.selected_child
  60.             catched_x = self.catched_x
  61.             catched_y = self.catched_y
  62.            
  63.             # Меняем их в соответствии с положением мышки.
  64.             widget.child_set_property(this, 'x', event.x - catched_x)
  65.             widget.child_set_property(this, 'y', event.y - catched_y)
  66.            
  67.    
  68.     def mouse_press(self, widget, event):
  69.        
  70.         # Эта функция сработает, если мы будем кликать где-ниубдь на окне.
  71.         # Нам нужно определить, на каком виджете оказалась мышь, когда мы кликнули.
  72.         for w in widget.get_children():
  73.             # Получаем необходимые координаты, а так же длинну и высоту виджета.
  74.             this_x = widget.child_get_property(w, 'x')
  75.             this_y = widget.child_get_property(w, 'y')
  76.             this_width, this_height = w.get_size_request()
  77.             # Простая проверка.
  78.             if this_x < event.x < this_width + this_x:
  79.                 if this_y < event.y < this_height + this_y:
  80.                     self.selected_child = w
  81.                     self.catched_x = event.x - this_x
  82.                     self.catched_y = event.y - this_y
  83.                     break
  84.    
  85.    
  86.     def mouse_release(self, widget, event):
  87.        
  88.         # Тут, думаю, всё очевидно.
  89.         self.selected_child = None
  90.    
  91.    
  92.  
  93. if __name__ == '__main__':
  94.     Application()
  95.     gtk.main()
  96.  
  97.    
  98.  
Отлично. Запускаем свежеприготовленный скрипт:

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

EventBox


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

А вот прилагающийся листинг:
  1.  
  2. #!/usr/bin/env python
  3. #-*- coding:utf-8 -*-
  4.  
  5. import pygtk
  6. pygtk.require('2.0')
  7.  
  8. import gtk
  9. import gtk.glade
  10.  
  11.  
  12. class Application(object):
  13.  
  14.    
  15.     def __init__(self):
  16.        
  17.         #
  18.        
  19.         self.gladefile = 'main.glade'
  20.        
  21.         self.widgets_tree = gtk.glade.XML(self.gladefile)
  22.        
  23.         main = {
  24.             'layout_press_event': self.layout_press,
  25.             'layout_release_event': self.layout_release }
  26.        
  27.         self.widgets_tree.signal_autoconnect(main)
  28.        
  29.         self.wget = self.widgets_tree.get_widget
  30.        
  31.         self.window = self.wget('window')
  32.        
  33.         assert self.window
  34.        
  35.         self.window.connect('destroy', self.close_app)
  36.        
  37.         #
  38.        
  39.         self.drawing = self.wget('drawing')
  40.        
  41.  
  42.     def close_app(self, widget):
  43.        
  44.         self.window.destroy()
  45.         gtk.main_quit()
  46.    
  47.    
  48.     def layout_press(self, wigdet, event):
  49.        
  50.         # Запоминаем координаты, где мы кликнули мышкой.
  51.         self.xy0 = (event.x, event.y)
  52.                
  53.    
  54.     def layout_release(self, widget, event):
  55.        
  56.         # Запоминаем координаты, где мышь была отпущенна и вызываем draw().
  57.         self.xy1 = (event.x, event.y)
  58.         self.draw()
  59.        
  60.        
  61.     def draw(self):
  62.        
  63.         # По поводу кода ниже - рекомендую познакомится со ссылкой:
  64.         # doc.crossplatform.ru/python/pygtk/2.4/ch-DrawingArea.html#sec-GraphicsContext
  65.         drawable = self.drawing.window
  66.         colormap = self.drawing.get_colormap()
  67.         assert drawable
  68.        
  69.         this_color = gtk.gdk.Color(red=0xffff, green=0x0, blue=0x0)
  70.         this_foreground = colormap.alloc_color(this_color)
  71.         gc = drawable.new_gc( foreground=this_foreground,
  72.                               background=this_foreground,
  73.                               line_width=2,
  74.                               line_style=gtk.gdk.LINE_SOLID,
  75.                               join_style=gtk.gdk.JOIN_MITER,
  76.                               cap_style=gtk.gdk.CAP_BUTT,
  77.                               fill=gtk.gdk.SOLID,
  78.                               function=gtk.gdk.COPY )
  79.        
  80.         x0, y0 = [int(x) for x in self.xy0]
  81.         x1, y1 = [int(x) for x in self.xy1]
  82.        
  83.         drawable.draw_line(gc, x0, y0, x1, y1)
  84.    
  85.  
  86.  
  87. if __name__ == '__main__':
  88.     Application()
  89.     gtk.main()
  90.  
  91.  
  92.  
Всё достаточно просто, не так ли? Результат работы будет выглядеть так:

Скрипт со всем прилагающимся можно взять тут.

А как реализовать это без glade?


Ну и на сладкое, как всё это сделать непосредственно кодом, без прибегания к помощи glade? Очень просто. Настолько просто, что я бы даже отказался бы от помощи glade в этом вопросе. Лишь попробуйте ввести это в интерпретаторе:
  1.  
  2. import gtk
  3. window = gtk.Window(gtk.WINDOW_TOPLEVEL)
  4. layout = gtk.Layout()
  5. # В этой строке и таится весь сакральный смысл.
  6. layout.set_events(gtk.gdk.BUTTON1_MASK|gtk.gdk.BUTTON3_MOTION_MASK)
  7. # Спешу заметить, что BUTTON3 - это вторая кнопка мыши, а не третья, как могло бы показаться.
  8. window.add(layout)
  9.  
  10. # пишем обработчик для всех событий. Пусть просто выводит в консоль что произошло.
  11. def event(widget, event):
  12. <div style="font: normal normal 1em/1.2em monospace; margin:0; padding:0; backgrou
+18
26 марта 2010, 13:35
26
DkZ 7,5

комментарии (10)

+2
AlexWinner #
Отличная статья, просто и понятно изложено, спасибо! Как раз хотел разобраться в PyGTK, она будет поводом:)

П.с. Поправьте, пожалуйста, «интерпритаторе» на «интерпретаторе».
+3
DkZ #
Спасибо за надзор над орфографией. :) Поправил.
+3
DkZ #
Однако вместе с тем потёрлось несколько строк топика.
+1
mapiobrothers #
Перенеси, чтоли, куда-нибудь ))
+1
TheShock #
кармы ему не хватает еще)
+1
mapiobrothers #
Печаль ;(
+1
spanasik #
Кармы добавил, переносите в блог PyGTK :-)
+1
DkZ #
Спасибо. Перенёс.
+2
spanasik #
Пожалуйста! Пишите побольше статей про PyGTK :-)
0
andy128k #
Использовать библиотеку libglade нежелательно. Она deprecated.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.