Package node_manager_fkie :: Module master_list_model
[frames] | no frames]

Source Code for Module node_manager_fkie.master_list_model

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Fraunhofer nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32   
 33  from python_qt_binding import QtCore 
 34  from python_qt_binding import QtGui 
 35   
 36  import threading 
 37   
 38  from urlparse import urlparse 
 39  from socket import getaddrinfo, AF_INET, AF_INET6 
 40   
 41  import node_manager_fkie as nm 
42 43 -class MasterSyncItem(QtGui.QStandardItem):
44 ITEM_TYPE = QtGui.QStandardItem.UserType + 35 45 46 NOT_SYNC = 0 47 START_SYNC = 1 48 SYNC = 2 49 50 ICON_PREFIX = 'irondevil' 51 # ICON_PREFIX = 'crystal_clear' 52
53 - def __init__(self, master, parent=None):
54 self.name = master.name 55 QtGui.QStandardItem.__init__(self, self.name) 56 self.parent_item = None 57 self._master = master 58 self._syncronized = MasterSyncItem.NOT_SYNC 59 self.ICONS = {MasterSyncItem.SYNC : QtGui.QIcon(":/icons/%s_sync.png"%self.ICON_PREFIX), 60 MasterSyncItem.NOT_SYNC : QtGui.QIcon(":/icons/%s_not_sync.png"%self.ICON_PREFIX), 61 MasterSyncItem.START_SYNC: QtGui.QIcon(":/icons/%s_start_sync.png"%self.ICON_PREFIX) } 62 self.setIcon(self.ICONS[MasterSyncItem.NOT_SYNC])
63 64 @property
65 - def master(self):
66 return self._master
67 68 @property
69 - def synchronized(self):
70 return self._syncronized
71 72 @synchronized.setter
73 - def synchronized(self, value):
74 if self._syncronized != value: 75 self._syncronized = value 76 if value in self.ICONS: 77 self.setIcon(self.ICONS[value])
78
79 - def __eq__(self, item):
80 if isinstance(item, str) or isinstance(item, unicode): 81 return self.master.name.lower() == item.lower() 82 elif not (item is None): 83 return self.master.name.lower() == item.master.name.lower() 84 return False
85
86 - def __gt__(self, item):
87 if isinstance(item, str) or isinstance(item, unicode): 88 return self.master.name.lower() > item.lower() 89 elif not (item is None): 90 return self.master.name.lower() > item.master.name.lower() 91 return False
92
93 94 -class MasterItem(QtGui.QStandardItem):
95 ''' 96 The master item stored in the master model. This class stores the master as 97 master_discovery_fkie.ROSMaster. 98 ''' 99 100 ITEM_TYPE = QtGui.QStandardItem.UserType + 34 101
102 - def __init__(self, master, local=False, quality=None, parent=None):
103 self.name = ''.join([master.name, ' (localhost)']) if local else master.name 104 QtGui.QStandardItem.__init__(self, self.name) 105 self.parent_item = None 106 self._master = master 107 self.__quality = quality 108 self.descr = '' 109 self.ICONS = {'green' : QtGui.QIcon(":/icons/stock_connect_green.png"), 110 'yellow': QtGui.QIcon(":/icons/stock_connect_yellow.png"), 111 'red' : QtGui.QIcon(":/icons/stock_connect_red.png"), 112 'grey' : QtGui.QIcon(":/icons/stock_connect.png"), 113 'disconnected' : QtGui.QIcon(":/icons/stock_disconnect.png"), 114 'warning' : QtGui.QIcon(':/icons/crystal_clear_warning.png') } 115 self.master_ip = None 116 self._master_errors = [] 117 self._threaded_get_ip() 118 self.updateNameView(master, quality, self)
119
120 - def _threaded_get_ip(self):
121 thread = threading.Thread(target=self.__get_ip) 122 thread.daemon = True 123 thread.start()
124
125 - def __get_ip(self):
126 try: 127 # get the IP of the master uri 128 o = urlparse(self.master.uri) 129 result = getaddrinfo(o.hostname, None) 130 ips = [] 131 for r in result: 132 if r[0] == AF_INET6: 133 (family, socktype, proto, canonname, (ip, port, flow, scope)) = r 134 else: 135 (family, socktype, proto, canonname, (ip, port)) = r 136 if self.master_ip is None and ip: 137 self.master_ip = '' 138 if ip and not ip in ips: 139 self.master_ip = ' '.join([self.master_ip, ip]) 140 ips.append(ip) 141 # self.updateNameView(self.master, self.quality, self) 142 except: 143 import traceback 144 print traceback.format_exc(1)
145 146 @property
147 - def master(self):
148 return self._master
149 150 @master.setter
151 - def master(self, value):
152 self._master = value
153 154 @property
155 - def quality(self):
156 return self.__quality
157 158 @quality.setter
159 - def quality(self, value):
160 if self.__quality != value: 161 self.__quality = value 162 self.updateMasterView(self.parent_item)
163
164 - def updateMasterErrors(self, error_list):
165 self._master_errors = error_list 166 self.updateNameView(self.master, self.quality, self)
167
168 - def updateMasterView(self, parent):
169 ''' 170 This method is called after the master state is changed to update the 171 representation of the master. The name will not be changed, but all other 172 data. 173 @param parent: Item which contains this master item. This is needed to update 174 other columns of this master. 175 @type parent: L{PySide.QtGui.QStandardItem} 176 ''' 177 if not parent is None: 178 #update the name decoration 179 child = parent.child(self.row(), MasterModel.COL_NAME) 180 if not child is None: 181 self.updateNameView(self.master, self.quality, child)
182
183 - def updateNameView(self, master, quality, item):
184 ''' 185 Updates the representation of the column contains the name state. 186 @param master: the topic data 187 @type master: master_discovery_fkie.TopicInfo 188 @param item: corresponding item in the model 189 @type item: L{TopicItem} 190 ''' 191 tooltip = ''.join(['<html><body>']) 192 tooltip = ''.join([tooltip, '<h4>', master.uri, '</h4>']) 193 tooltip = ''.join([tooltip, '<dl>']) 194 tooltip = ''.join([tooltip, '<dt>', 'IP: ', str(self.master_ip), '</dt>']) 195 if master.online: 196 if not quality is None and quality != -1.: 197 tooltip = ''.join([tooltip, '<dt>', 'Quality: ', str(quality),' %', '</dt>']) 198 else: 199 tooltip = ''.join([tooltip, '<dt>', 'Quality: not available</dt>']) 200 # if item.checkState() == QtCore.Qt.Checked: 201 # tooltip = ''.join([tooltip, '<dt>', 'synchronized', '</dt>']) 202 else: 203 tooltip = ''.join([tooltip, '<dt>', 'offline', '</dt>']) 204 tooltip = ''.join([tooltip, '</dl>']) 205 if item.descr: 206 # tooltip = ''.join([tooltip, '<b><u>Description:</u></b>']) 207 tooltip = ''.join([tooltip, item.descr]) 208 # update the icon 209 if master.online: 210 if self._master_errors or self.master_ip is None: 211 item.setIcon(self.ICONS['warning']) 212 if self.master_ip is None: 213 tooltip = ''.join([tooltip, '<h4>', '<font color="#CC0000">Host not reachable by name!!! The ROS topics may not by connected!!!</font>', '</h4>']) 214 if self._master_errors: 215 tooltip = ''.join([tooltip, '<h4>Errors reported by master_discovery:</h4>']) 216 for err in self._master_errors: 217 tooltip = ''.join([tooltip, '<dt><font color="#CC0000">%s</font></dt>'%err]) 218 elif not quality is None and quality != -1.: 219 if quality > 30: 220 item.setIcon(self.ICONS['green']) 221 elif quality > 5: 222 item.setIcon(self.ICONS['yellow']) 223 else: 224 item.setIcon(self.ICONS['red']) 225 else: 226 item.setIcon(self.ICONS['grey']) 227 else: 228 item.setIcon(self.ICONS['disconnected']) 229 230 tooltip = ''.join([tooltip, '</body></html>']) 231 item.setToolTip(tooltip)
232
233 - def updateDescription(self, descr):
234 self.descr = descr 235 self.updateNameView(self.master, self.quality, self)
236 237 @classmethod
238 - def toHTML(cls, text):
239 ''' 240 @param text: the text 241 @type text: C{str} 242 @return: the HTML representation of the name of the text 243 @rtype: C{str} 244 ''' 245 ns, sep, name = text.rpartition('/') 246 result = '' 247 if sep: 248 result = ''.join(['<html><body>', '<span style="color:gray;">', str(ns), sep, '</span><b>', name, '</b></body></html>']) 249 else: 250 result = name 251 return result
252
253 - def type(self):
254 return MasterItem.ITEM_TYPE
255 256 @classmethod
257 - def getItemList(self, master, local):
258 ''' 259 Creates the list of the items from master. This list is used for the 260 visualization of master data as a table row. 261 @param master the master data 262 @type master master_discovery_fkie.ROSMaster 263 @param local: whether the master is local or not 264 @type local: bool 265 @return: the list for the representation as a row 266 @rtype: C{[L{MasterItem} or L{PySide.QtGui.QStandardItem}, ...]} 267 ''' 268 items = [] 269 item = MasterSyncItem(master) 270 items.append(item) 271 item = MasterItem(master, local) 272 items.append(item) 273 return items
274
275 - def __eq__(self, item):
276 if isinstance(item, str) or isinstance(item, unicode): 277 return self.master.name.lower() == item.lower() 278 elif not (item is None): 279 return self.master.name.lower() == item.master.name.lower() 280 return False
281
282 - def __gt__(self, item):
283 if isinstance(item, str) or isinstance(item, unicode): 284 return self.master.name.lower() > item.lower() 285 elif not (item is None): 286 return self.master.name.lower() > item.master.name.lower() 287 return False
288
289 290 291 -class MasterModel(QtGui.QStandardItemModel):
292 ''' 293 The model to manage the list with masters in ROS network. 294 ''' 295 header = [('Sync', 22), ('Name', -1)] 296 '''@ivar: the list with columns C{[(name, width), ...]}''' 297 COL_SYNC = 0 298 COL_NAME = 1 299
300 - def __init__(self, local_masteruri=None):
301 ''' 302 Creates a new list model. 303 ''' 304 QtGui.QStandardItemModel.__init__(self) 305 self.setColumnCount(len(MasterModel.header)) 306 self._masteruri = local_masteruri 307 self.pyqt_workaround = dict() # workaround for using with PyQt: store the python object to keep the defined attributes in the MasterItem subclass
308
309 - def flags(self, index):
310 ''' 311 @param index: parent of the list 312 @type index: L{PySide.QtCore.QModelIndex} 313 @return: Flag or the requestet item 314 @rtype: L{PySide.QtCore.Qt.ItemFlag} 315 @see: U{http://www.pyside.org/docs/pyside-1.0.1/PySide/QtCore/Qt.html} 316 ''' 317 if not index.isValid(): 318 return QtCore.Qt.NoItemFlags 319 # item = self.itemFromIndex(index) 320 # if item and item.master.online: 321 return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
322 # return QtCore.Qt.NoItemFlags 323
324 - def updateMaster(self, master):
325 ''' 326 Updates the information of the ros master. If the ROS master not exists, it 327 will be added. 328 329 @param master: the ROS master to update 330 @type master: L{master_discovery_fkie.msg.ROSMaster} 331 ''' 332 # remove master, if his name was changed but not the ROS master URI 333 root = self.invisibleRootItem() 334 for i in reversed(range(root.rowCount())): 335 masterItem = root.child(i) 336 if masterItem.master.uri == master.uri and masterItem.master.name != master.name: 337 root.removeRow(i) 338 try: 339 del self.pyqt_workaround[masterItem.master.name] 340 except: 341 pass 342 break 343 344 # update or add a the item 345 root = self.invisibleRootItem() 346 doAddItem = True 347 for i in range(root.rowCount()): 348 masterItem = root.child(i, self.COL_NAME) 349 if (masterItem == master.name): 350 # update item 351 masterItem.master = master 352 masterItem.updateMasterView(root) 353 doAddItem = False 354 break 355 elif (masterItem > master.name): 356 mitem = MasterItem.getItemList(master, (nm.is_local(nm.nameres().getHostname(master.uri)))) 357 self.pyqt_workaround[master.name] = mitem # workaround for using with PyQt: store the python object to keep the defined attributes in the MasterItem subclass 358 root.insertRow(i, mitem) 359 mitem[self.COL_NAME].parent_item = root 360 doAddItem = False 361 break 362 if doAddItem: 363 mitem = MasterItem.getItemList(master, (nm.is_local(nm.nameres().getHostname(master.uri)))) 364 self.pyqt_workaround[master.name] = mitem # workaround for using with PyQt: store the python object to keep the defined attributes in the MasterItem subclass 365 root.appendRow(mitem) 366 mitem[self.COL_NAME].parent_item = root
367
368 - def updateMasterStat(self, master, quality):
369 ''' 370 Updates the information of the ros master. 371 372 @param master: the ROS master to update 373 @type master: C{str} 374 @param quality: the quality of the connection to master 375 @type quality: C{float} 376 ''' 377 root = self.invisibleRootItem() 378 for i in reversed(range(root.rowCount())): 379 masterItem = root.child(i, self.COL_NAME) 380 if masterItem.master.name in master: 381 masterItem.quality = quality 382 break
383
384 - def setChecked(self, master, state):
385 ''' 386 Set the master to checked state 387 388 @param master: the ROS master to update 389 @type master: C{str} 390 @param state: new state 391 @type state: C{bool} 392 ''' 393 root = self.invisibleRootItem() 394 for i in reversed(range(root.rowCount())): 395 masterItem = root.child(i, self.COL_SYNC) 396 if masterItem.master.name == master: 397 masterItem.synchronized = MasterSyncItem.SYNC if state else MasterSyncItem.NOT_SYNC 398 break
399
400 - def removeMaster(self, master):
401 ''' 402 Remove the master with given name. 403 404 @param master: the ROS master to add 405 @type master: C{str} 406 ''' 407 root = self.invisibleRootItem() 408 for i in reversed(range(root.rowCount())): 409 masterItem = root.child(i, self.COL_NAME) 410 if masterItem.master.name == master: 411 root.removeRow(i) 412 try: 413 del self.pyqt_workaround_sync[masterItem.master.name] 414 del self.pyqt_workaround_info[masterItem.master.name] 415 except: 416 pass 417 break
418
419 - def updateMasterErrors(self, master, errors):
420 ''' 421 Updates the errors reported by master_discovery. 422 423 @param master: the ROS master to update 424 @type master: C{str} 425 @param errors: the list with errors 426 @type errors: C{[str]} 427 ''' 428 root = self.invisibleRootItem() 429 for i in reversed(range(root.rowCount())): 430 masterItem = root.child(i, self.COL_NAME) 431 if masterItem.master.name in master: 432 masterItem.updateMasterErrors(errors) 433 break
434
435 - def updateDescription(self, master, descr):
436 ''' 437 Updates the description of the master with given name. 438 439 @param master: the ROS master to add 440 @type master: C{str} 441 @param descr: the description of the master coded as HTML 442 @type descr: C{str} 443 ''' 444 root = self.invisibleRootItem() 445 for i in range(root.rowCount()): 446 masterItem = root.child(i, self.COL_NAME) 447 if masterItem and masterItem.master.name == master: 448 masterItem.updateDescription(descr)
449