root/trunk/loplug/plugins/input/pginput.py

Revision 1944, 22.6 KB (checked in by dart, 4 months ago)

I18n:
o Last translations update

LoPlug?:
o Fix last deliveries for logger (need more work later)

Line 
1# -*- coding: utf-8 -*-
2# vim: ts=4:sw=4
3#    This file is part of LOME.
4#
5#    LOME is free software: you can redistribute it and/or modify
6#    it under the terms of the GNU General Public License as published by
7#    the Free Software Foundation, either version 3 of the License, or
8#    (at your option) any later version.
9#
10#    LOME is distributed in the hope that it will be useful,
11#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#    GNU General Public License for more details.
14#
15#    You should have received a copy of the GNU General Public License
16#    along with LOME.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19import pythoncom
20import sys
21__pyhook_loaded = False
22__pyhook_msg = []
23try:
24    import pyHook
25    __pyhook_loaded = True
26except:
27    import traceback
28    exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
29    msg_a = traceback.format_exception(exceptionType, exceptionValue, exceptionTraceback)
30    msg = ""
31    for m in msg_a:
32        msg += m
33    __pyhook_msg.append( msg )
34try:
35    import PyHook as pyHook
36    __pyhook_loaded = True
37except:
38    import traceback
39    exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
40    msg_a = traceback.format_exception(exceptionType, exceptionValue, exceptionTraceback)
41    msg = ""
42    for m in msg_a:
43        msg += m
44    __pyhook_msg.append( msg )
45
46if not __pyhook_loaded:
47    print "CANNOT LOAD PYHOOK"
48    for m in __pyhook_msg:
49        print m
50import ctypes
51import win32com.client
52
53from pgpluginclient   import *
54from szobjects        import *
55 
56#----------------------------------------------------------------
57# Declaration of common variables to PGInput and static def
58pgInputInstance          = None     #- Instance of the plugin
59mouseEventClientCallback = None     #- Client call back used by pyHook for mouse events
60keyPressed               = []       #- Table which carries the keys pressed
61
62#----------------------------------------------------------------
63## Call back invoked on keyboard event
64# @param event            event object
65def onKeyBoardEvent( event):
66  # Process this event
67  key = event.ScanCode
68  if event.IsTransition():
69    if key in keyPressed:
70      keyPressed.remove( key)
71      pgInputInstance.emit( SIGNAL("keyReleased"), keyPressed)
72  else:
73#    print '----------------------------------'
74#    print 'Ascii      :', event.Ascii, chr(event.Ascii)
75#    print 'Key        :', event.Key
76#    print 'KeyID      :', event.KeyID
77#    print 'ScanCode   :', event.ScanCode
78#    print 'Extended   :', event.Extended
79#    print 'Injected   :', event.Injected
80#    print 'Alt        :', event.Alt
81    if not ( key in keyPressed):
82      keyPressed.append( key)
83      pgInputInstance.addKeyAssociation( key, event.Key)
84      pgInputInstance.emit( SIGNAL("keyPressed"), keyPressed)
85     
86  return True
87#----------------------------------------------------------------
88## Call back invoked on mouse event
89# @param event    event object
90#                 event.MessageName -> "mouse move"                             --> associated with event.Position
91#                                      "mouse left up"   / "mouse left down"
92#                                      "mouse middle up" / "mouse middle down"
93#                                      "mouse rigth up"  / "mouse rigth down"
94#                                      "mouse wheel"
95#                                      "wheel"                                  --> associated with event.Wheel
96#                 event.Position    -> (x,y)
97#                 event.Wheel       -> +1 / -1
98def onMouseEvent( event):
99  # Get instance of PGInput
100  self = pgInputInstance
101 
102  # We do not intercept mouseMove events
103  if ( event.MessageName == "mouse move") or ( not self):
104    return True
105
106  # Get mouse position
107  xMouse = event.Position[0]
108  yMouse = event.Position[1]
109
110  # Check if there are some client to notify
111  returnedValue = True
112  plugId        = []
113  for srcPludId, datas in self.requests[PGInput.MOUSE_EVENT_ID].iteritems():
114    if ( xMouse >= datas[PGInput.X_INDEX]                              ) and\
115       ( xMouse <= datas[PGInput.X_INDEX] + datas[PGInput.WIDTH_INDEX] ) and\
116       ( yMouse >= datas[PGInput.Y_INDEX]                              ) and\
117       ( yMouse <= datas[PGInput.Y_INDEX] + datas[PGInput.HEIGHT_INDEX]) :
118      plugId.append( srcPludId)
119     
120      # Do not forward the event
121      returnedValue = False
122 
123  # Send data
124  if not returnedValue:
125    # Build object and send it
126    szObject = SzMouseEvent( from_id  = self.get_name()
127                           , data_id  = self.build_full_itemId( PGInput.MOUSE_EVENT_ID)
128                           , event    = event.MessageName
129                           , position = event.Position
130                           , wheel    = event.Wheel
131                           )
132    self.send_object( plugId, PGPluginClient.OPCODE_DATA_VALUE, szObject)
133 
134  return returnedValue
135 
136
137 
138#================================================================
139## This class subclasses the QStandardItem to provide item for tableView
140class CmdxxxItem( QStandardItem):
141  def __init__( self, name):
142    QStandardItem.__init__( self, name)
143   
144#================================================================
145## This class is used to gather data associated to a command
146class CmdItem():
147  def __init__( self, cmdId):
148    self.__cmdId__  = cmdId
149    self.__name__   = cst_get_last_part_of_address( cmdId)
150    self.clearCombo()
151   
152  def getId( self):
153    return self.__cmdId__
154   
155  def getNameItem( self):
156    name = CmdxxxItem( self.__name__)
157    name.setEditable( False)
158    return name
159  def getComboItem( self):
160    keys = CmdxxxItem( self.__comboText__)
161    keys.setEditable( False)
162    return keys
163   
164  def clearCombo( self):
165    self.__combo__       = []
166    self.__comboLength__ = 0
167    self.__comboText__   = ""
168  def setCombo( self, combo):
169    self.__combo__       = combo
170    self.__comboLength__ = len( combo)
171    self.__comboText__   = ""
172    for key in combo:
173      if pgInputInstance.keyAssociation.has_key( key):
174        self.__comboText__ += pgInputInstance.keyAssociation[key] + "  "
175      else:
176        self.clearCombo()
177        return
178  def addKeyToCombo( self, key):
179    if pgInputInstance.keyAssociation.has_key( key) and not ( key in self.__combo__):
180      self.__combo__.append( key)
181      self.__comboLength__ += 1
182      self.__comboText__   += pgInputInstance.keyAssociation[key] + "  "
183      return True
184    return False
185  def getCombo( self):
186    return self.__combo__
187  def hasCombo( self, combo):
188    if len(combo) == self.__comboLength__:
189      for key in combo:
190        if not ( key in self.__combo__):
191          return False
192      return True
193    return False
194 
195 
196#================================================================
197## Main class in python to manage connection with server
198class PGInput(PGPluginClient):
199
200  MOUSE_EVENT_ID = CST_LOPLUG_REQUEST + "MouseEventId"
201  X_INDEX        = 0
202  Y_INDEX        = 1
203  WIDTH_INDEX    = 2
204  HEIGHT_INDEX   = 3
205 
206  # Index used in TableView
207  CMD_NAME_INDEX = 0
208  COMBO_INDEX    = 1
209
210  # Working mode of the module
211  WORKING_MODE_NOMINAL = 0
212  WORKING_MODE_EDITION = 1
213
214  infos = { "description": "Input plugin"
215          , "version"    : 0.1
216          , "icon"       : "input.jpg"
217          }
218#----------------------------------------------------------------
219  ## Constructor
220  # @param self              instance class
221  # @param config            config
222  def __init__( self, config, pathname):
223    PGPluginClient.__init__( self, config, pathname)
224   
225    # Here config has been read, and config variables are set
226     
227#================================================================
228# OVERRIDDEN METHODS
229#================================================================
230
231#----------------------------------------------------------------
232  # Create all plug variables.
233  # Method invoked from __init__() method of the base class
234  # @param self        instance class
235  def _create_plug_context( self):
236    self.capabilities = { "provider":[
237                                     ],   #- List of datas that the plugin can provide [name, id, description, type, default format]
238                          "renderer":[ CST_LOPLUG_CAPA_CMD
239                                     ]    #- List of capabilities for plugins (see constants.py for a list)
240                        }
241
242    # Init variable which are part or issued of the config.
243    global pgInputInstance
244    pgInputInstance     = self                           #- Instance of PGInput, used by pyHook callback methodes
245    self.clientId       = None                           #- ClientId for mouseEvent notification
246    self.requests       = {}                             #- Discard all the requests from every plug
247    self.keyAssociation = {}                             #- Association KeyValue/KeyName for the used key
248    self.row            = 0                              #- Identify the current selected row
249    self.cmdList        = []                             #- list of command items
250    self.workingMode    = PGInput.WORKING_MODE_NOMINAL
251   
252    # Do Hook stuff
253    self.hookManager = pyHook.HookManager()
254    self.connect( self, SIGNAL("keyPressed"), self.__onKeyPressed__ )
255   
256    # Subscribed to keyboard events
257    self.hookManager.SubscribeKeyAll( onKeyBoardEvent)
258    self.hookManager.HookKeyboard()
259   
260    # Set mouseEvent call back. Service activation is done by the client
261    self.mouseSubscribed = False
262    self.connect( self, SIGNAL("EnableMouseHook") , self.__enableMouseHook__)
263    self.connect( self, SIGNAL("DisableMouseHook"), self.__disableMouseHook__)
264#----------------------------------------------------------------
265  # To load the settings
266  # Method invoked from __init__() method of the base class
267  # @param self        instance class
268  def _load_config( self ):
269    # Read key association
270    self.keyAssociation = {}
271    value = self.get_config_value( "keyAssociation")
272    if value:
273      pairs = value.split("|")
274      for pair in pairs:
275        keyValue, keyName = pair.split(":")
276        self.keyAssociation[int( keyValue)] = keyName
277     
278    # Read CmdConfig 
279    self.cmdList = []
280    value = self.get_config_value( "CmdConfig")
281    if value:
282      cmdConfigs = value.split("##")
283      for cmdConfig in cmdConfigs:
284        keyId, keyCombo = cmdConfig.split(":")
285        # Create cmdItem
286        cmdItem = CmdItem( keyId)
287        # Process combo for this cmdItem
288        keyComboStr = keyCombo.split("|")
289        keyCombo = []
290        for key in keyComboStr:
291          if key != "":
292            keyCombo.append( int( key))
293        cmdItem.setCombo( keyCombo)
294        self.cmdList.append( cmdItem)
295
296#----------------------------------------------------------------
297  # To save the settings
298  # @param self        instance class
299  def _save_config( self ):
300    # Save key association
301    keyAssociation = ""
302    for keyValue, keyName in self.keyAssociation.iteritems():
303      keyAssociation += "%s:%s|" % ( keyValue, keyName)
304    keyAssociation = keyAssociation[:-1]
305    self.set_config_value( "keyAssociation", keyAssociation)
306   
307    # Save Combo
308    keyCombo = ""
309    for cmdItem in self.cmdList:
310      cmdId    = cmdItem.getId()
311      cmdKeys  = cmdItem.getCombo()
312      keyCombo += "%s:" % cmdId
313      if len( cmdKeys):
314        for cmdKey in cmdKeys:
315          keyCombo += "%s|" % cmdKey
316        keyCombo = keyCombo[:-1]
317      else:
318        keyCombo += "|"
319      keyCombo += "##"
320    keyCombo = keyCombo[:-2]
321    self.set_config_value( "CmdConfig", keyCombo)
322   
323    self.workingMode    = PGInput.WORKING_MODE_NOMINAL
324#----------------------------------------------------------------
325  # To get configuration widget.
326  # It is used to get the teamspeak executable and DLL path
327  # @param self        instance class
328  # @return widget
329  def _get_config_widget( self, data):
330    self.selectedIndex = None
331    self.workingMode   = PGInput.WORKING_MODE_NOMINAL
332
333    #- List of all commands managed by the module
334    self._updateCmdList__( data)
335   
336    # Build
337    self.main = QWidget()
338    hBox = QHBoxLayout()
339    self.main.setLayout( hBox)
340   
341    self.cmdView  = QTableView( self.main)
342    self.cmdView.horizontalHeader().setStretchLastSection( True)
343    self.cmdView.horizontalHeader().setResizeMode( PGInput.COMBO_INDEX, QHeaderView.Stretch)
344    self.cmdModel = QStandardItemModel( 0, 2, self.main)
345    self.cmdView.setModel( self.cmdModel)
346   
347    hBox.addWidget( self.cmdView)
348   
349    vBox = QVBoxLayout()
350    lblExplain = QLabel( self.tr(
351    """Way of working:
352    1) Enter 'EDIT' mode
353    2) Select the combo to change"
354    3) Change the combo, enter key by key
355    4) Repeat operations 2) and 3)
356    5) Exit 'EDIT' mode
357   
358    COMBO:
359      All the keys pressed at the same time
360      This is why you can not have the same key two times
361    """) )
362    lblExplain.setWordWrap( True )
363    vBox.addWidget( lblExplain)
364    vBox.addStretch(0)   
365    self.btnEditMode = QPushButton()
366    vBox.addWidget( self.btnEditMode)
367    vBox.addStretch(0)
368
369    hBox.addLayout( vBox)
370   
371    self.connect( self.cmdView    , SIGNAL( "clicked(const QModelIndex&)"), self.__onCmdItemSelected__ )
372    self.connect( self.btnEditMode, SIGNAL( "clicked(bool)"              ), self.__onChangeEditMode__  )
373   
374    self.__updateUi__()
375
376   
377    return self.main
378#----------------------------------------------------------------
379  # Transfert data from 'model' to 'UI'
380  # @param self instance class
381  def _init_ui( self, options=None ):
382    pass
383
384#----------------------------------------------------------------
385  # Transfert data from 'UI' to 'model'
386  # @param self instance class
387  def _deinit_ui( self ):
388    pass
389
390#----------------------------------------------------------------
391  # Method invoked by the PGPluginClient parent on start()
392  # When this method is invoke the config has been loaded
393  # So, here execute start specific part.
394  # @param self                      instance class
395  def _start( self ):    # Get instance of hook manager
396    pass
397#----------------------------------------------------------------
398  # Method invoked by the PGPluginClient parent on restart()
399  # When this method is invoke the config has been loaded
400  # So, here execute restart specific part.
401  # @param self                      instance class
402  def _config_has_changed( self ):
403    pass
404#----------------------------------------------------------------
405  # Provider datas has been updated -> Change the UI
406  # @param self instance class
407  def _update_provider_data( self, data ):
408    self._updateCmdList__( data)
409    self.__updateUi__()
410#----------------------------------------------------------------
411  # A client request data
412  # So, here execute restart specific part.
413  # @param self                      instance class
414  # @param szDataRequest             Data for this request
415  def _data_requested( self, szDataRequest): 
416    # Create entry for this data if not yet done
417    dataId = szDataRequest.data_id
418    if not self.requests.has_key( dataId):
419      self.requests[dataId] = {}
420     
421      if dataId == PGInput.MOUSE_EVENT_ID:
422        # Enable hook mouse process
423        self.debug( "Enable mouse hook -----------------------------------------------------------" )
424        self.emit( SIGNAL("EnableMouseHook"))
425
426    srcPlugId = szDataRequest.from_id
427    if not ( srcPlugId in self.requests[dataId].keys()):
428      self.requests[dataId][srcPlugId] = []
429
430    if dataId == PGInput.MOUSE_EVENT_ID:
431      self.requests[dataId][srcPlugId] = [ szDataRequest.x, szDataRequest.y, szDataRequest.width, szDataRequest.height]
432#       print "Mouse event request, x[%s], y[%s], w[%s], h[%s]" % (szDataRequest.x, szDataRequest.y, szDataRequest.width, szDataRequest.height)
433
434
435#----------------------------------------------------------------
436  # To receive data discard commands
437  # @param self                      instance class
438  # @param srcPlugId                 Id of the requester plug (renderer)
439  # @param datas                     datas
440  def _data_discarded( self, szDataDiscard):
441    dataId = szDataDiscard.data_id
442    if self.requests.has_key( dataId):
443      srcPlugId = szDataDiscard.from_id
444      if srcPlugId in self.requests[dataId].keys():
445        del self.requests[dataId][srcPlugId]
446        if not self.requests[dataId]:
447          del self.requests[dataId]
448          if dataId == PGInput.MOUSE_EVENT_ID:
449            # Disable hook mouse process
450            self.emit( SIGNAL("DisableMouseHook"))
451
452  #----------------------------------------------------------------
453  # A selection occurs on the channel list -> switch
454  # @param self                      instance class
455  # @param srcPlugId                 Id of the plug sending the data
456  # @param list_id                   list Id
457  # @param datas                     datas
458  def _selection( self, srcPlugId, list_id, datas):
459    pass   
460#----------------------------------------------------------------
461  # To shutdown the instance
462  # To be overwritten by the daughter class if needed
463  # @param self                      instance class
464  def _shutDown( self):
465    pass
466#----------------------------------------------------------------
467  # Wait for shutdown complete
468  # @param self                      instance class
469  def _waitForShutDownComplete(self):
470    pass
471   
472#================================================================
473# INTERFACE METHODS
474#================================================================
475  def addKeyAssociation( self, keyId, keyName):
476    if not self.keyAssociation.has_key( keyId):
477      self.keyAssociation[keyId] = keyName
478     
479#================================================================
480# LOCAL METHODS
481#================================================================
482#----------------------------------------------------------------
483  # Invoked on receiving signal "EnableMouseHook"
484  # @param self        instance class
485  def __enableMouseHook__( self):
486    # To perform mouse subscription only onece
487    if self.mouseSubscribed == False:
488      self.mouseSubscribed = True
489      self.hookManager.SubscribeMouseAll( onMouseEvent)   
490    self.hookManager.HookMouse()
491#----------------------------------------------------------------
492  # Invoked on receiving signal "DisableMouseHook"
493  # @param self        instance class   
494  def __disableMouseHook__( self):
495    self.hookManager.UnhookMouse()
496#----------------------------------------------------------------
497  # Call back method invoked when a key is pressed
498  # @param self                      instance class
499  # @param keyTable                  table of keyCodes for all the key pressed
500  def __onKeyPressed__( self, keyTable):
501    if self.workingMode == PGInput.WORKING_MODE_EDITION:
502      if self.cmdItem:
503        for key in keyTable:
504          newKey = self.cmdItem.addKeyToCombo( key)
505          if newKey:
506            self.__updateUi__()
507     
508    else:
509      for cmdItem in self.cmdList:
510        if cmdItem.hasCombo( keyTable):
511          cmdId = cmdItem.getId()
512          opCode = PGPluginClient.OPCODE_DATA_VALUE
513          szObj = SzText( from_id = self.get_name()
514                        , data_id = cmdId
515                        , text    = ""
516                        )
517          self.send_object([cmdId], opCode, szObj )
518          # We stop at the first item found.
519          # But it is possible to continue if we want many commands associated to a unic combo
520          break
521           
522#----------------------------------------------------------------
523  # This method updates the list of cmdItem containing all the command to be processed
524  # @param self                      instance class
525  # @param data                      data related to all_providers
526  def  _updateCmdList__( self, data):
527    newList = []
528    rendered_datas = self.get_list_data_to_render_for()
529    for providerName, providerInfoList in rendered_datas.iteritems():
530      for thisProviderInfo in providerInfoList:
531        cmdItem = None
532        for providerInfo in data["all_providers_data"]:
533          if ( thisProviderInfo.id == providerInfo.id) and providerInfo.visible:
534            cmdItem = self.__searchForCmdItem__( thisProviderInfo.id)
535            if cmdItem:
536              break
537        if not cmdItem    :
538          cmdItem = CmdItem( thisProviderInfo.id)
539         
540        newList.append( cmdItem)
541       
542    self.cmdList = newList
543#----------------------------------------------------------------
544  # This method searchs in teh cmdList if the cmdId exits
545  # @param self                      instance class
546  # @param cmdId                     id of the command to search
547  def __searchForCmdItem__( self, cmdId):
548    for cmdItem in self.cmdList:
549      if cmdItem.getId() == cmdId:
550        return cmdItem
551    return None
552#----------------------------------------------------------------
553  # This method updates the UI according to the cmdList.
554  # @param self                      instance class
555  def __updateUi__( self):
556    self.cmdModel.clear()
557    self.cmdModel.setHorizontalHeaderLabels( [ self.tr("Command name"), self.tr("Combo")])
558    row = 0
559    for cmdItem in self.cmdList:
560      self.cmdModel.appendRow( [ cmdItem.getNameItem(), cmdItem.getComboItem()])
561      self.cmdModel.setVerticalHeaderItem( row, QStandardItem())
562      row += 1
563    self.cmdView.resizeColumnsToContents()
564    if self.workingMode == PGInput.WORKING_MODE_NOMINAL:
565      self.btnEditMode.setText( self.tr( "Enter EDIT mode"))
566      self.cmdView.setStyleSheet( "background-color: white;"
567                                  "selection-background-color: pink;")
568    else:
569      self.btnEditMode.setText( self.tr( "Exit EDIT nmode"))
570      self.cmdView.setStyleSheet( "background-color: white;"
571                                  "selection-background-color: cyan;")
572
573
574    if self.selectedIndex:
575      index = self.cmdModel.index( self.selectedIndex[0], PGInput.CMD_NAME_INDEX)
576      self.cmdView.setCurrentIndex( index)
577#----------------------------------------------------------------
578  # Call back method invoked when 'ChangeCombo' key is pressed.
579  # @param self                      instance class
580  # @param clicked                   unused
581  def __onChangeEditMode__( self, clicked):
582    if self.workingMode == PGInput.WORKING_MODE_NOMINAL:
583      self.workingMode = PGInput.WORKING_MODE_EDITION
584    else:
585      self.workingMode = PGInput.WORKING_MODE_NOMINAL
586     
587    self.cmdItem = None
588    self.selectedIndex = None
589    self.__updateUi__()
590#----------------------------------------------------------------
591  # Call back method invoked when a cell is selected
592  # @param self                      instance class
593  # @param index                     provides info on the selected cell
594  def __onCmdItemSelected__( self, index):
595    self.cmdItem = self.cmdList[index.row()]
596    if self.workingMode == PGInput.WORKING_MODE_EDITION:
597      self.cmdItem.clearCombo()
598      self.selectedIndex = [index.row(), index.column()]
599      self.__updateUi__()
600
Note: See TracBrowser for help on using the browser.