/ src / bitmessageqt / foldertree.py.bak
foldertree.py.bak
  1  """
  2  Folder tree and messagelist widgets definitions.
  3  """
  4  # pylint: disable=too-many-arguments,bad-super-call
  5  # pylint: disable=attribute-defined-outside-init
  6  
  7  from cgi import escape
  8  
  9  from PyQt4 import QtCore, QtGui
 10  
 11  from bmconfigparser import config
 12  from helper_sql import sqlExecute, sqlQuery
 13  from settingsmixin import SettingsMixin
 14  from tr import _translate
 15  from utils import avatarize
 16  
 17  # for pylupdate
 18  _translate("MainWindow", "inbox")
 19  _translate("MainWindow", "new")
 20  _translate("MainWindow", "sent")
 21  _translate("MainWindow", "trash")
 22  
 23  TimestampRole = QtCore.Qt.UserRole + 1
 24  
 25  
 26  class AccountMixin(object):
 27      """UI-related functionality for accounts"""
 28      ALL = 0
 29      NORMAL = 1
 30      CHAN = 2
 31      MAILINGLIST = 3
 32      SUBSCRIPTION = 4
 33      BROADCAST = 5
 34  
 35      def accountColor(self):
 36          """QT UI color for an account"""
 37          if not self.isEnabled:
 38              return QtGui.QColor(128, 128, 128)
 39          elif self.type == self.CHAN:
 40              return QtGui.QColor(216, 119, 0)
 41          elif self.type in [self.MAILINGLIST, self.SUBSCRIPTION]:
 42              return QtGui.QColor(137, 4, 177)
 43          return QtGui.QApplication.palette().text().color()
 44  
 45      def folderColor(self):
 46          """QT UI color for a folder"""
 47          if not self.parent().isEnabled:
 48              return QtGui.QColor(128, 128, 128)
 49          return QtGui.QApplication.palette().text().color()
 50  
 51      def accountBrush(self):
 52          """Account brush (for QT UI)"""
 53          brush = QtGui.QBrush(self.accountColor())
 54          brush.setStyle(QtCore.Qt.NoBrush)
 55          return brush
 56  
 57      def folderBrush(self):
 58          """Folder brush (for QT UI)"""
 59          brush = QtGui.QBrush(self.folderColor())
 60          brush.setStyle(QtCore.Qt.NoBrush)
 61          return brush
 62  
 63      def accountString(self):
 64          """Account string suitable for use in To: field: label <address>"""
 65          label = self._getLabel()
 66          return (
 67              self.address if label == self.address
 68              else '%s <%s>' % (label, self.address)
 69          )
 70  
 71      def setAddress(self, address):
 72          """Set bitmessage address of the object"""
 73          if address is None:
 74              self.address = None
 75          else:
 76              self.address = str(address)
 77  
 78      def setUnreadCount(self, cnt):
 79          """Set number of unread messages"""
 80          try:
 81              if self.unreadCount == int(cnt):
 82                  return
 83          except AttributeError:
 84              pass
 85          self.unreadCount = int(cnt)
 86          if isinstance(self, QtGui.QTreeWidgetItem):
 87              self.emitDataChanged()
 88  
 89      def setEnabled(self, enabled):
 90          """Set account enabled (QT UI)"""
 91          self.isEnabled = enabled
 92          try:
 93              self.setExpanded(enabled)
 94          except AttributeError:
 95              pass
 96          if isinstance(self, Ui_AddressWidget):
 97              for i in range(self.childCount()):
 98                  if isinstance(self.child(i), Ui_FolderWidget):
 99                      self.child(i).setEnabled(enabled)
100          if isinstance(self, QtGui.QTreeWidgetItem):
101              self.emitDataChanged()
102  
103      def setType(self):
104          """Set account type (QT UI)"""
105          self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable)
106          if self.address is None:
107              self.type = self.ALL
108              self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable)
109          elif config.safeGetBoolean(self.address, 'chan'):
110              self.type = self.CHAN
111          elif config.safeGetBoolean(self.address, 'mailinglist'):
112              self.type = self.MAILINGLIST
113          elif sqlQuery(
114                  '''select label from subscriptions where address=?''', self.address):
115              self.type = AccountMixin.SUBSCRIPTION
116          else:
117              self.type = self.NORMAL
118  
119      def defaultLabel(self):
120          """Default label (in case no label is set manually)"""
121          queryreturn = None
122          retval = None
123          if self.type in (
124                  AccountMixin.NORMAL,
125                  AccountMixin.CHAN, AccountMixin.MAILINGLIST):
126              try:
127                  retval = unicode(
128                      config.get(self.address, 'label'), 'utf-8')
129              except Exception:
130                  queryreturn = sqlQuery(
131                      '''select label from addressbook where address=?''', self.address)
132          elif self.type == AccountMixin.SUBSCRIPTION:
133              queryreturn = sqlQuery(
134                  '''select label from subscriptions where address=?''', self.address)
135          if queryreturn is not None:
136              if queryreturn != []:
137                  for row in queryreturn:
138                      retval, = row
139                      retval = unicode(retval, 'utf-8')
140          elif self.address is None or self.type == AccountMixin.ALL:
141              return unicode(
142                  str(_translate("MainWindow", "All accounts")), 'utf-8')
143  
144          return retval or unicode(self.address, 'utf-8')
145  
146  
147  class BMTreeWidgetItem(QtGui.QTreeWidgetItem, AccountMixin):
148      """A common abstract class for Tree widget item"""
149  
150      def __init__(self, parent, pos, address, unreadCount):
151          super(QtGui.QTreeWidgetItem, self).__init__()
152          self.setAddress(address)
153          self.setUnreadCount(unreadCount)
154          self._setup(parent, pos)
155  
156      def _getAddressBracket(self, unreadCount=False):
157          return " (" + str(self.unreadCount) + ")" if unreadCount else ""
158  
159      def data(self, column, role):
160          """Override internal QT method for returning object data"""
161          if column == 0:
162              if role == QtCore.Qt.DisplayRole:
163                  return self._getLabel() + self._getAddressBracket(
164                      self.unreadCount > 0)
165              elif role == QtCore.Qt.EditRole:
166                  return self._getLabel()
167              elif role == QtCore.Qt.ToolTipRole:
168                  return self._getLabel() + self._getAddressBracket(False)
169              elif role == QtCore.Qt.FontRole:
170                  font = QtGui.QFont()
171                  font.setBold(self.unreadCount > 0)
172                  return font
173          return super(BMTreeWidgetItem, self).data(column, role)
174  
175  
176  class Ui_FolderWidget(BMTreeWidgetItem):
177      """Item in the account/folder tree representing a folder"""
178      folderWeight = {"inbox": 1, "new": 2, "sent": 3, "trash": 4}
179  
180      def __init__(
181              self, parent, pos=0, address="", folderName="", unreadCount=0):
182          self.setFolderName(folderName)
183          super(Ui_FolderWidget, self).__init__(
184              parent, pos, address, unreadCount)
185  
186      def _setup(self, parent, pos):
187          parent.insertChild(pos, self)
188  
189      def _getLabel(self):
190          return _translate("MainWindow", self.folderName)
191  
192      def setFolderName(self, fname):
193          """Set folder name (for QT UI)"""
194          self.folderName = str(fname)
195  
196      def data(self, column, role):
197          """Override internal QT method for returning object data"""
198          if column == 0 and role == QtCore.Qt.ForegroundRole:
199              return self.folderBrush()
200          return super(Ui_FolderWidget, self).data(column, role)
201  
202      # inbox, sent, thrash first, rest alphabetically
203      def __lt__(self, other):
204          if isinstance(other, Ui_FolderWidget):
205              if self.folderName in self.folderWeight:
206                  x = self.folderWeight[self.folderName]
207              else:
208                  x = 99
209              if other.folderName in self.folderWeight:
210                  y = self.folderWeight[other.folderName]
211              else:
212                  y = 99
213              reverse = QtCore.Qt.DescendingOrder == \
214                  self.treeWidget().header().sortIndicatorOrder()
215              if x == y:
216                  return self.folderName < other.folderName
217              return x >= y if reverse else x < y
218  
219          return super(QtGui.QTreeWidgetItem, self).__lt__(other)
220  
221  
222  class Ui_AddressWidget(BMTreeWidgetItem, SettingsMixin):
223      """Item in the account/folder tree representing an account"""
224      def __init__(self, parent, pos=0, address=None, unreadCount=0, enabled=True):
225          super(Ui_AddressWidget, self).__init__(
226              parent, pos, address, unreadCount)
227          self.setEnabled(enabled)
228  
229      def _setup(self, parent, pos):
230          self.setType()
231          parent.insertTopLevelItem(pos, self)
232  
233      def _getLabel(self):
234          if self.address is None:
235              return unicode(_translate(
236                  "MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore')
237          else:
238              try:
239                  return unicode(
240                      config.get(self.address, 'label'),
241                      'utf-8', 'ignore')
242              except:
243                  return unicode(self.address, 'utf-8')
244  
245      def _getAddressBracket(self, unreadCount=False):
246          ret = "" if self.isExpanded() \
247              else super(Ui_AddressWidget, self)._getAddressBracket(unreadCount)
248          if self.address is not None:
249              ret += " (" + self.address + ")"
250          return ret
251  
252      def data(self, column, role):
253          """Override internal QT method for returning object data"""
254          if column == 0:
255              if role == QtCore.Qt.DecorationRole:
256                  return avatarize(
257                      self.address or self._getLabel().encode('utf8'))
258              elif role == QtCore.Qt.ForegroundRole:
259                  return self.accountBrush()
260          return super(Ui_AddressWidget, self).data(column, role)
261  
262      def setData(self, column, role, value):
263          """Save account label (if you edit in the the UI, this will be triggered and will save it to keys.dat)"""
264          if role == QtCore.Qt.EditRole \
265                  and self.type != AccountMixin.SUBSCRIPTION:
266              config.set(
267                  str(self.address), 'label',
268                  str(value.toString().toUtf8())
269                  if isinstance(value, QtCore.QVariant)
270                  else value.encode('utf-8')
271              )
272              config.save()
273          return super(Ui_AddressWidget, self).setData(column, role, value)
274  
275      def setAddress(self, address):
276          """Set address to object (for QT UI)"""
277          super(Ui_AddressWidget, self).setAddress(address)
278          self.setData(0, QtCore.Qt.UserRole, self.address)
279  
280      def _getSortRank(self):
281          return self.type if self.isEnabled else (self.type + 100)
282  
283      # label (or address) alphabetically, disabled at the end
284      def __lt__(self, other):
285          # pylint: disable=protected-access
286          if isinstance(other, Ui_AddressWidget):
287              reverse = QtCore.Qt.DescendingOrder == \
288                  self.treeWidget().header().sortIndicatorOrder()
289              if self._getSortRank() == other._getSortRank():
290                  x = self._getLabel().lower()
291                  y = other._getLabel().lower()
292                  return x < y
293              return (
294                  not reverse
295                  if self._getSortRank() < other._getSortRank() else reverse
296              )
297  
298          return super(QtGui.QTreeWidgetItem, self).__lt__(other)
299  
300  
301  class Ui_SubscriptionWidget(Ui_AddressWidget):
302      """Special treating of subscription addresses"""
303      # pylint: disable=unused-argument
304      def __init__(self, parent, pos=0, address="", unreadCount=0, label="", enabled=True):
305          super(Ui_SubscriptionWidget, self).__init__(
306              parent, pos, address, unreadCount, enabled)
307  
308      def _getLabel(self):
309          queryreturn = sqlQuery(
310              '''select label from subscriptions where address=?''', self.address)
311          if queryreturn != []:
312              for row in queryreturn:
313                  retval, = row
314              return unicode(retval, 'utf-8', 'ignore')
315          return unicode(self.address, 'utf-8')
316  
317      def setType(self):
318          """Set account type"""
319          super(Ui_SubscriptionWidget, self).setType()  # sets it editable
320          self.type = AccountMixin.SUBSCRIPTION  # overrides type
321  
322      def setData(self, column, role, value):
323          """Save subscription label to database"""
324          if role == QtCore.Qt.EditRole:
325              if isinstance(value, QtCore.QVariant):
326                  label = str(
327                      value.toString().toUtf8()).decode('utf-8', 'ignore')
328              else:
329                  label = unicode(value, 'utf-8', 'ignore')
330              sqlExecute(
331                  '''UPDATE subscriptions SET label=? WHERE address=?''',
332                  label, self.address)
333          return super(Ui_SubscriptionWidget, self).setData(column, role, value)
334  
335  
336  class BMTableWidgetItem(QtGui.QTableWidgetItem, SettingsMixin):
337      """A common abstract class for Table widget item"""
338  
339      def __init__(self, label=None, unread=False):
340          super(QtGui.QTableWidgetItem, self).__init__()
341          self.setLabel(label)
342          self.setUnread(unread)
343          self._setup()
344  
345      def _setup(self):
346          self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
347  
348      def setLabel(self, label):
349          """Set object label"""
350          self.label = label
351  
352      def setUnread(self, unread):
353          """Set/unset read state of an item"""
354          self.unread = unread
355  
356      def data(self, role):
357          """Return object data (QT UI)"""
358          if role in (
359              QtCore.Qt.DisplayRole, QtCore.Qt.EditRole, QtCore.Qt.ToolTipRole
360          ):
361              return self.label
362          elif role == QtCore.Qt.FontRole:
363              font = QtGui.QFont()
364              font.setBold(self.unread)
365              return font
366          return super(BMTableWidgetItem, self).data(role)
367  
368  
369  class BMAddressWidget(BMTableWidgetItem, AccountMixin):
370      """A common class for Table widget item with account"""
371  
372      def _setup(self):
373          super(BMAddressWidget, self)._setup()
374          self.setEnabled(True)
375          self.setType()
376  
377      def _getLabel(self):
378          return self.label
379  
380      def data(self, role):
381          """Return object data (QT UI)"""
382          if role == QtCore.Qt.ToolTipRole:
383              return self.label + " (" + self.address + ")"
384          elif role == QtCore.Qt.DecorationRole:
385              if config.safeGetBoolean(
386                      'bitmessagesettings', 'useidenticons'):
387                  return avatarize(self.address or self.label)
388          elif role == QtCore.Qt.ForegroundRole:
389              return self.accountBrush()
390          return super(BMAddressWidget, self).data(role)
391  
392  
393  class MessageList_AddressWidget(BMAddressWidget):
394      """Address item in a messagelist"""
395      def __init__(self, address=None, label=None, unread=False):
396          self.setAddress(address)
397          super(MessageList_AddressWidget, self).__init__(label, unread)
398  
399      def setLabel(self, label=None):
400          """Set label"""
401          super(MessageList_AddressWidget, self).setLabel(label)
402          if label is not None:
403              return
404          newLabel = self.address
405          queryreturn = None
406          if self.type in (
407                  AccountMixin.NORMAL,
408                  AccountMixin.CHAN, AccountMixin.MAILINGLIST):
409              try:
410                  newLabel = unicode(
411                      config.get(self.address, 'label'),
412                      'utf-8', 'ignore')
413              except:
414                  queryreturn = sqlQuery(
415                      '''select label from addressbook where address=?''', self.address)
416          elif self.type == AccountMixin.SUBSCRIPTION:
417              queryreturn = sqlQuery(
418                  '''select label from subscriptions where address=?''', self.address)
419          if queryreturn:
420              for row in queryreturn:
421                  newLabel = unicode(row[0], 'utf-8', 'ignore')
422  
423          self.label = newLabel
424  
425      def data(self, role):
426          """Return object data (QT UI)"""
427          if role == QtCore.Qt.UserRole:
428              return self.address
429          return super(MessageList_AddressWidget, self).data(role)
430  
431      def setData(self, role, value):
432          """Set object data"""
433          if role == QtCore.Qt.EditRole:
434              self.setLabel()
435          return super(MessageList_AddressWidget, self).setData(role, value)
436  
437      # label (or address) alphabetically, disabled at the end
438      def __lt__(self, other):
439          if isinstance(other, MessageList_AddressWidget):
440              return self.label.lower() < other.label.lower()
441          return super(QtGui.QTableWidgetItem, self).__lt__(other)
442  
443  
444  class MessageList_SubjectWidget(BMTableWidgetItem):
445      """Message list subject item"""
446      def __init__(self, subject=None, label=None, unread=False):
447          self.setSubject(subject)
448          super(MessageList_SubjectWidget, self).__init__(label, unread)
449  
450      def setSubject(self, subject):
451          """Set subject"""
452          self.subject = subject
453  
454      def data(self, role):
455          """Return object data (QT UI)"""
456          if role == QtCore.Qt.UserRole:
457              return self.subject
458          if role == QtCore.Qt.ToolTipRole:
459              return escape(unicode(self.subject, 'utf-8'))
460          return super(MessageList_SubjectWidget, self).data(role)
461  
462      # label (or address) alphabetically, disabled at the end
463      def __lt__(self, other):
464          if isinstance(other, MessageList_SubjectWidget):
465              return self.label.lower() < other.label.lower()
466          return super(QtGui.QTableWidgetItem, self).__lt__(other)
467  
468  
469  # In order for the time columns on the Inbox and Sent tabs to be sorted
470  # correctly (rather than alphabetically), we need to overload the <
471  # operator and use this class instead of QTableWidgetItem.
472  class MessageList_TimeWidget(BMTableWidgetItem):
473      """
474      A subclass of QTableWidgetItem for received (lastactiontime) field.
475      '<' operator is overloaded to sort by TimestampRole == 33
476      msgid is available by QtCore.Qt.UserRole
477      """
478  
479      def __init__(self, label=None, unread=False, timestamp=None, msgid=''):
480          super(MessageList_TimeWidget, self).__init__(label, unread)
481          self.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid))
482          self.setData(TimestampRole, int(timestamp))
483  
484      def __lt__(self, other):
485          return self.data(TimestampRole) < other.data(TimestampRole)
486  
487      def data(self, role=QtCore.Qt.UserRole):
488          """
489          Returns expected python types for QtCore.Qt.UserRole and TimestampRole
490          custom roles and super for any Qt role
491          """
492          data = super(MessageList_TimeWidget, self).data(role)
493          if role == TimestampRole:
494              return int(data.toPyObject())
495          if role == QtCore.Qt.UserRole:
496              return str(data.toPyObject())
497          return data
498  
499  
500  class Ui_AddressBookWidgetItem(BMAddressWidget):
501      """Addressbook item"""
502      # pylint: disable=unused-argument
503      def __init__(self, label=None, acc_type=AccountMixin.NORMAL):
504          self.type = acc_type
505          super(Ui_AddressBookWidgetItem, self).__init__(label=label)
506  
507      def data(self, role):
508          """Return object data"""
509          if role == QtCore.Qt.UserRole:
510              return self.type
511          return super(Ui_AddressBookWidgetItem, self).data(role)
512  
513      def setData(self, role, value):
514          """Set data"""
515          if role == QtCore.Qt.EditRole:
516              self.label = str(
517                  value.toString().toUtf8()
518                  if isinstance(value, QtCore.QVariant) else value
519              )
520              if self.type in (
521                      AccountMixin.NORMAL,
522                      AccountMixin.MAILINGLIST, AccountMixin.CHAN):
523                  try:
524                      config.get(self.address, 'label')
525                      config.set(self.address, 'label', self.label)
526                      config.save()
527                  except:
528                      sqlExecute('''UPDATE addressbook set label=? WHERE address=?''', self.label, self.address)
529              elif self.type == AccountMixin.SUBSCRIPTION:
530                  sqlExecute('''UPDATE subscriptions set label=? WHERE address=?''', self.label, self.address)
531              else:
532                  pass
533          return super(Ui_AddressBookWidgetItem, self).setData(role, value)
534  
535      def __lt__(self, other):
536          if isinstance(other, Ui_AddressBookWidgetItem):
537              reverse = QtCore.Qt.DescendingOrder == \
538                  self.tableWidget().horizontalHeader().sortIndicatorOrder()
539  
540              if self.type == other.type:
541                  return self.label.lower() < other.label.lower()
542              return not reverse if self.type < other.type else reverse
543          return super(QtGui.QTableWidgetItem, self).__lt__(other)
544  
545  
546  class Ui_AddressBookWidgetItemLabel(Ui_AddressBookWidgetItem):
547      """Addressbook label item"""
548      def __init__(self, address, label, acc_type):
549          self.address = address
550          super(Ui_AddressBookWidgetItemLabel, self).__init__(label, acc_type)
551  
552      def data(self, role):
553          """Return object data"""
554          self.label = self.defaultLabel()
555          return super(Ui_AddressBookWidgetItemLabel, self).data(role)
556  
557  
558  class Ui_AddressBookWidgetItemAddress(Ui_AddressBookWidgetItem):
559      """Addressbook address item"""
560      def __init__(self, address, label, acc_type):
561          self.address = address
562          super(Ui_AddressBookWidgetItemAddress, self).__init__(address, acc_type)
563  
564      def data(self, role):
565          """Return object data"""
566          if role == QtCore.Qt.ToolTipRole:
567              return self.address
568          if role == QtCore.Qt.DecorationRole:
569              return None
570          return super(Ui_AddressBookWidgetItemAddress, self).data(role)
571  
572  
573  class AddressBookCompleter(QtGui.QCompleter):
574      """Addressbook completer"""
575  
576      def __init__(self):
577          super(AddressBookCompleter, self).__init__()
578          self.cursorPos = -1
579  
580      def onCursorPositionChanged(self, oldPos, newPos):  # pylint: disable=unused-argument
581          """Callback for cursor position change"""
582          if oldPos != self.cursorPos:
583              self.cursorPos = -1
584  
585      def splitPath(self, path):
586          """Split on semicolon"""
587          text = unicode(path.toUtf8(), 'utf-8')
588          return [text[:self.widget().cursorPosition()].split(';')[-1].strip()]
589  
590      def pathFromIndex(self, index):
591          """Perform autocompletion (reimplemented QCompleter method)"""
592          autoString = unicode(
593              index.data(QtCore.Qt.EditRole).toString().toUtf8(), 'utf-8')
594          text = unicode(self.widget().text().toUtf8(), 'utf-8')
595  
596          # If cursor position was saved, restore it, else save it
597          if self.cursorPos != -1:
598              self.widget().setCursorPosition(self.cursorPos)
599          else:
600              self.cursorPos = self.widget().cursorPosition()
601  
602          # Get current prosition
603          curIndex = self.widget().cursorPosition()
604  
605          # prev_delimiter_index should actually point at final white space
606          # AFTER the delimiter
607          # Get index of last delimiter before current position
608          prevDelimiterIndex = text[0:curIndex].rfind(";")
609          while text[prevDelimiterIndex + 1] == " ":
610              prevDelimiterIndex += 1
611  
612          # Get index of first delimiter after current position
613          # (or EOL if no delimiter after cursor)
614          nextDelimiterIndex = text.find(";", curIndex)
615          if nextDelimiterIndex == -1:
616              nextDelimiterIndex = len(text)
617  
618          # Get part of string that occurs before cursor
619          part1 = text[0:prevDelimiterIndex + 1]
620  
621          # Get string value from before auto finished string is selected
622          # pre = text[prevDelimiterIndex + 1:curIndex - 1]
623  
624          # Get part of string that occurs AFTER cursor
625          part2 = text[nextDelimiterIndex:]
626  
627          return part1 + autoString + part2