/ src / bitmessageqt / __init__.py
__init__.py
   1  """
   2  PyQt based UI for bitmessage, the main module
   3  """
   4  
   5  import hashlib
   6  import locale
   7  import os
   8  import random
   9  import string
  10  import subprocess
  11  import sys
  12  import textwrap
  13  import threading
  14  import time
  15  from datetime import datetime, timedelta
  16  from sqlite3 import register_adapter
  17  
  18  from PyQt4 import QtCore, QtGui
  19  from PyQt4.QtNetwork import QLocalSocket, QLocalServer
  20  
  21  import shared
  22  import state
  23  from debug import logger
  24  from tr import _translate
  25  from .account import (
  26      accountClass, getSortedSubscriptions,
  27      BMAccount, GatewayAccount, MailchuckAccount, AccountColor)
  28  from addresses import decodeAddress, addBMIfNotPresent
  29  from .bitmessageui import Ui_MainWindow
  30  from bmconfigparser import config
  31  import namecoin
  32  from .messageview import MessageView
  33  from .migrationwizard import Ui_MigrationWizard
  34  from .foldertree import (
  35      AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget,
  36      MessageList_AddressWidget, MessageList_SubjectWidget,
  37      Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress,
  38      MessageList_TimeWidget)
  39  from . import settingsmixin
  40  from . import support
  41  from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure
  42  import helper_addressbook
  43  import helper_search
  44  import l10n
  45  from .utils import str_broadcast_subscribers, avatarize
  46  from . import dialogs
  47  from network.stats import pendingDownload, pendingUpload
  48  from .uisignaler import UISignaler
  49  import paths
  50  from proofofwork import getPowType
  51  import queues
  52  import shutdown
  53  from .statusbar import BMStatusBar
  54  from . import sound
  55  # This is needed for tray icon
  56  from . import bitmessage_icons_rc  # noqa:F401 pylint: disable=unused-import
  57  import helper_sent
  58  
  59  try:
  60      from plugins.plugin import get_plugin, get_plugins
  61  except ImportError:
  62      get_plugins = False
  63  
  64  
  65  is_windows = sys.platform.startswith('win')
  66  
  67  
  68  # TODO: rewrite
  69  def powQueueSize():
  70      """Returns the size of queues.workerQueue including current unfinished work"""
  71      queue_len = queues.workerQueue.qsize()
  72      for thread in threading.enumerate():
  73          try:
  74              if thread.name == "singleWorker":
  75                  queue_len += thread.busy
  76          except Exception as err:
  77              logger.info('Thread error %s', err)
  78      return queue_len
  79  
  80  
  81  def openKeysFile():
  82      """Open keys file with an external editor"""
  83      keysfile = os.path.join(state.appdata, 'keys.dat')
  84      if 'linux' in sys.platform:
  85          subprocess.call(["xdg-open", keysfile])
  86      elif is_windows:
  87          os.startfile(keysfile)  # pylint: disable=no-member
  88  
  89  
  90  class MyForm(settingsmixin.SMainWindow):
  91  
  92      # the maximum frequency of message sounds in seconds
  93      maxSoundFrequencySec = 60
  94  
  95      REPLY_TYPE_SENDER = 0
  96      REPLY_TYPE_CHAN = 1
  97      REPLY_TYPE_UPD = 2
  98  
  99      def change_translation(self, newlocale=None):
 100          """Change translation language for the application"""
 101          if newlocale is None:
 102              newlocale = l10n.getTranslationLanguage()
 103          try:
 104              if not self.qmytranslator.isEmpty():
 105                  QtGui.QApplication.removeTranslator(self.qmytranslator)
 106          except:
 107              pass
 108          try:
 109              if not self.qsystranslator.isEmpty():
 110                  QtGui.QApplication.removeTranslator(self.qsystranslator)
 111          except:
 112              pass
 113  
 114          self.qmytranslator = QtCore.QTranslator()
 115          translationpath = os.path.join(
 116              paths.codePath(), 'translations', 'bitmessage_' + newlocale)
 117          self.qmytranslator.load(translationpath)
 118          QtGui.QApplication.installTranslator(self.qmytranslator)
 119  
 120          self.qsystranslator = QtCore.QTranslator()
 121          if paths.frozen:
 122              translationpath = os.path.join(
 123                  paths.codePath(), 'translations', 'qt_' + newlocale)
 124          else:
 125              translationpath = os.path.join(
 126                  str(QtCore.QLibraryInfo.location(
 127                      QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale)
 128          self.qsystranslator.load(translationpath)
 129          QtGui.QApplication.installTranslator(self.qsystranslator)
 130  
 131          # TODO: move this block into l10n
 132          # FIXME: shouldn't newlocale be used here?
 133          lang = locale.normalize(l10n.getTranslationLanguage())
 134          langs = [
 135              lang.split(".")[0] + "." + l10n.encoding,
 136              lang.split(".")[0] + "." + 'UTF-8',
 137              lang
 138          ]
 139          if 'win32' in sys.platform or 'win64' in sys.platform:
 140              langs = [l10n.getWindowsLocale(lang)]
 141          for lang in langs:
 142              try:
 143                  l10n.setlocale(lang)
 144                  if 'win32' not in sys.platform and 'win64' not in sys.platform:
 145                      l10n.encoding = locale.nl_langinfo(locale.CODESET)
 146                  else:
 147                      l10n.encoding = locale.getlocale()[1]
 148                  logger.info("Successfully set locale to %s", lang)
 149                  break
 150              except:
 151                  logger.error("Failed to set locale to %s", lang, exc_info=True)
 152  
 153      def init_file_menu(self):
 154          QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL(
 155              "triggered()"), self.quit)
 156          QtCore.QObject.connect(self.ui.actionNetworkSwitch, QtCore.SIGNAL(
 157              "triggered()"), self.network_switch)
 158          QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL(
 159              "triggered()"), self.click_actionManageKeys)
 160          QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages,
 161                                 QtCore.SIGNAL(
 162                                     "triggered()"),
 163                                 self.click_actionDeleteAllTrashedMessages)
 164          QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses,
 165                                 QtCore.SIGNAL(
 166                                     "triggered()"),
 167                                 self.click_actionRegenerateDeterministicAddresses)
 168          QtCore.QObject.connect(
 169              self.ui.pushButtonAddChan,
 170              QtCore.SIGNAL("clicked()"),
 171              self.click_actionJoinChan) # also used for creating chans.
 172          QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL(
 173              "clicked()"), self.click_NewAddressDialog)
 174          QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL(
 175              "clicked()"), self.click_pushButtonAddAddressBook)
 176          QtCore.QObject.connect(self.ui.pushButtonAddSubscription, QtCore.SIGNAL(
 177              "clicked()"), self.click_pushButtonAddSubscription)
 178          QtCore.QObject.connect(self.ui.pushButtonTTL, QtCore.SIGNAL(
 179              "clicked()"), self.click_pushButtonTTL)
 180          QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL(
 181              "clicked()"), self.click_pushButtonClear)
 182          QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL(
 183              "clicked()"), self.click_pushButtonSend)
 184          QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL(
 185              "clicked()"), self.click_pushButtonFetchNamecoinID)
 186          QtCore.QObject.connect(self.ui.actionSettings, QtCore.SIGNAL(
 187              "triggered()"), self.click_actionSettings)
 188          QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL(
 189              "triggered()"), self.click_actionAbout)
 190          QtCore.QObject.connect(self.ui.actionSupport, QtCore.SIGNAL(
 191              "triggered()"), self.click_actionSupport)
 192          QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL(
 193              "triggered()"), self.click_actionHelp)
 194  
 195      def init_inbox_popup_menu(self, connectSignal=True):
 196          # Popup menu for the Inbox tab
 197          self.ui.inboxContextMenuToolbar = QtGui.QToolBar()
 198          # Actions
 199          self.actionReply = self.ui.inboxContextMenuToolbar.addAction(_translate(
 200              "MainWindow", "Reply to sender"), self.on_action_InboxReply)
 201          self.actionReplyChan = self.ui.inboxContextMenuToolbar.addAction(_translate(
 202              "MainWindow", "Reply to channel"), self.on_action_InboxReplyChan)
 203          self.actionAddSenderToAddressBook = self.ui.inboxContextMenuToolbar.addAction(
 204              _translate(
 205                  "MainWindow", "Add sender to your Address Book"),
 206              self.on_action_InboxAddSenderToAddressBook)
 207          self.actionAddSenderToBlackList = self.ui.inboxContextMenuToolbar.addAction(
 208              _translate(
 209                  "MainWindow", "Add sender to your Blacklist"),
 210              self.on_action_InboxAddSenderToBlackList)
 211          self.actionTrashInboxMessage = self.ui.inboxContextMenuToolbar.addAction(
 212              _translate("MainWindow", "Move to Trash"),
 213              self.on_action_InboxTrash)
 214          self.actionUndeleteTrashedMessage = self.ui.inboxContextMenuToolbar.addAction(
 215              _translate("MainWindow", "Undelete"),
 216              self.on_action_TrashUndelete)
 217          self.actionForceHtml = self.ui.inboxContextMenuToolbar.addAction(
 218              _translate(
 219                  "MainWindow", "View HTML code as formatted text"),
 220              self.on_action_InboxMessageForceHtml)
 221          self.actionSaveMessageAs = self.ui.inboxContextMenuToolbar.addAction(
 222              _translate(
 223                  "MainWindow", "Save message as..."),
 224              self.on_action_InboxSaveMessageAs)
 225          self.actionMarkUnread = self.ui.inboxContextMenuToolbar.addAction(
 226              _translate(
 227                  "MainWindow", "Mark Unread"), self.on_action_InboxMarkUnread)
 228  
 229          # contextmenu messagelists
 230          self.ui.tableWidgetInbox.setContextMenuPolicy(
 231              QtCore.Qt.CustomContextMenu)
 232          if connectSignal:
 233              self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL(
 234                  'customContextMenuRequested(const QPoint&)'),
 235                  self.on_context_menuInbox)
 236          self.ui.tableWidgetInboxSubscriptions.setContextMenuPolicy(
 237              QtCore.Qt.CustomContextMenu)
 238          if connectSignal:
 239              self.connect(self.ui.tableWidgetInboxSubscriptions, QtCore.SIGNAL(
 240                  'customContextMenuRequested(const QPoint&)'),
 241                  self.on_context_menuInbox)
 242          self.ui.tableWidgetInboxChans.setContextMenuPolicy(
 243              QtCore.Qt.CustomContextMenu)
 244          if connectSignal:
 245              self.connect(self.ui.tableWidgetInboxChans, QtCore.SIGNAL(
 246                  'customContextMenuRequested(const QPoint&)'),
 247                  self.on_context_menuInbox)
 248  
 249      def init_identities_popup_menu(self, connectSignal=True):
 250          # Popup menu for the Your Identities tab
 251          self.ui.addressContextMenuToolbarYourIdentities = QtGui.QToolBar()
 252          # Actions
 253          self.actionNewYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(_translate(
 254              "MainWindow", "New"), self.on_action_YourIdentitiesNew)
 255          self.actionEnableYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 256              _translate(
 257                  "MainWindow", "Enable"), self.on_action_Enable)
 258          self.actionDisableYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 259              _translate(
 260                  "MainWindow", "Disable"), self.on_action_Disable)
 261          self.actionSetAvatarYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 262              _translate(
 263                  "MainWindow", "Set avatar..."),
 264              self.on_action_TreeWidgetSetAvatar)
 265          self.actionClipboardYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 266              _translate(
 267                  "MainWindow", "Copy address to clipboard"),
 268              self.on_action_Clipboard)
 269          self.actionSpecialAddressBehaviorYourIdentities = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 270              _translate(
 271                  "MainWindow", "Special address behavior..."),
 272              self.on_action_SpecialAddressBehaviorDialog)
 273          self.actionEmailGateway = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 274              _translate(
 275                  "MainWindow", "Email gateway"),
 276              self.on_action_EmailGatewayDialog)
 277          self.actionMarkAllRead = self.ui.addressContextMenuToolbarYourIdentities.addAction(
 278              _translate(
 279                  "MainWindow", "Mark all messages as read"),
 280              self.on_action_MarkAllRead)
 281  
 282          self.ui.treeWidgetYourIdentities.setContextMenuPolicy(
 283              QtCore.Qt.CustomContextMenu)
 284          if connectSignal:
 285              self.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL(
 286                  'customContextMenuRequested(const QPoint&)'),
 287                  self.on_context_menuYourIdentities)
 288  
 289          # load all gui.menu plugins with prefix 'address'
 290          self.menu_plugins = {'address': []}
 291          if not get_plugins:
 292              return
 293          for plugin in get_plugins('gui.menu', 'address'):
 294              try:
 295                  handler, title = plugin(self)
 296              except TypeError:
 297                  continue
 298              self.menu_plugins['address'].append(
 299                  self.ui.addressContextMenuToolbarYourIdentities.addAction(
 300                      title, handler
 301                  ))
 302  
 303      def init_chan_popup_menu(self, connectSignal=True):
 304          # Actions
 305          self.actionNew = self.ui.addressContextMenuToolbar.addAction(_translate(
 306              "MainWindow", "New"), self.on_action_YourIdentitiesNew)
 307          self.actionDelete = self.ui.addressContextMenuToolbar.addAction(
 308              _translate("MainWindow", "Delete"),
 309              self.on_action_YourIdentitiesDelete)
 310          self.actionEnable = self.ui.addressContextMenuToolbar.addAction(
 311              _translate(
 312                  "MainWindow", "Enable"), self.on_action_Enable)
 313          self.actionDisable = self.ui.addressContextMenuToolbar.addAction(
 314              _translate(
 315                  "MainWindow", "Disable"), self.on_action_Disable)
 316          self.actionSetAvatar = self.ui.addressContextMenuToolbar.addAction(
 317              _translate(
 318                  "MainWindow", "Set avatar..."),
 319              self.on_action_TreeWidgetSetAvatar)
 320          self.actionClipboard = self.ui.addressContextMenuToolbar.addAction(
 321              _translate(
 322                  "MainWindow", "Copy address to clipboard"),
 323              self.on_action_Clipboard)
 324          self.actionSend = self.ui.addressContextMenuToolbar.addAction(
 325              _translate("MainWindow", "Send message to this chan"),
 326              self.on_action_Send)
 327          self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction(
 328              _translate(
 329                  "MainWindow", "Special address behavior..."),
 330              self.on_action_SpecialAddressBehaviorDialog)
 331  
 332          self.ui.treeWidgetChans.setContextMenuPolicy(
 333              QtCore.Qt.CustomContextMenu)
 334          if connectSignal:
 335              self.connect(self.ui.treeWidgetChans, QtCore.SIGNAL(
 336                  'customContextMenuRequested(const QPoint&)'),
 337                  self.on_context_menuChan)
 338  
 339      def init_addressbook_popup_menu(self, connectSignal=True):
 340          # Popup menu for the Address Book page
 341          self.ui.addressBookContextMenuToolbar = QtGui.QToolBar()
 342          # Actions
 343          self.actionAddressBookSend = self.ui.addressBookContextMenuToolbar.addAction(
 344              _translate(
 345                  "MainWindow", "Send message to this address"),
 346              self.on_action_AddressBookSend)
 347          self.actionAddressBookClipboard = self.ui.addressBookContextMenuToolbar.addAction(
 348              _translate(
 349                  "MainWindow", "Copy address to clipboard"),
 350              self.on_action_AddressBookClipboard)
 351          self.actionAddressBookSubscribe = self.ui.addressBookContextMenuToolbar.addAction(
 352              _translate(
 353                  "MainWindow", "Subscribe to this address"),
 354              self.on_action_AddressBookSubscribe)
 355          self.actionAddressBookSetAvatar = self.ui.addressBookContextMenuToolbar.addAction(
 356              _translate(
 357                  "MainWindow", "Set avatar..."),
 358              self.on_action_AddressBookSetAvatar)
 359          self.actionAddressBookSetSound = \
 360              self.ui.addressBookContextMenuToolbar.addAction(
 361                  _translate("MainWindow", "Set notification sound..."),
 362                  self.on_action_AddressBookSetSound)
 363          self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction(
 364              _translate(
 365                  "MainWindow", "Add New Address"), self.on_action_AddressBookNew)
 366          self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction(
 367              _translate(
 368                  "MainWindow", "Delete"), self.on_action_AddressBookDelete)
 369          self.ui.tableWidgetAddressBook.setContextMenuPolicy(
 370              QtCore.Qt.CustomContextMenu)
 371          if connectSignal:
 372              self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL(
 373                  'customContextMenuRequested(const QPoint&)'),
 374                  self.on_context_menuAddressBook)
 375  
 376      def init_subscriptions_popup_menu(self, connectSignal=True):
 377          # Actions
 378          self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction(
 379              _translate("MainWindow", "New"), self.on_action_SubscriptionsNew)
 380          self.actionsubscriptionsDelete = self.ui.subscriptionsContextMenuToolbar.addAction(
 381              _translate("MainWindow", "Delete"),
 382              self.on_action_SubscriptionsDelete)
 383          self.actionsubscriptionsClipboard = self.ui.subscriptionsContextMenuToolbar.addAction(
 384              _translate("MainWindow", "Copy address to clipboard"),
 385              self.on_action_SubscriptionsClipboard)
 386          self.actionsubscriptionsEnable = self.ui.subscriptionsContextMenuToolbar.addAction(
 387              _translate("MainWindow", "Enable"),
 388              self.on_action_SubscriptionsEnable)
 389          self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction(
 390              _translate("MainWindow", "Disable"),
 391              self.on_action_SubscriptionsDisable)
 392          self.actionsubscriptionsSetAvatar = self.ui.subscriptionsContextMenuToolbar.addAction(
 393              _translate("MainWindow", "Set avatar..."),
 394              self.on_action_TreeWidgetSetAvatar)
 395          self.actionsubscriptionsSend = self.ui.addressContextMenuToolbar.addAction(
 396              _translate("MainWindow", "Send message to this address"),
 397              self.on_action_Send)
 398          self.ui.treeWidgetSubscriptions.setContextMenuPolicy(
 399              QtCore.Qt.CustomContextMenu)
 400          if connectSignal:
 401              self.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL(
 402                  'customContextMenuRequested(const QPoint&)'),
 403                  self.on_context_menuSubscriptions)
 404  
 405      def init_sent_popup_menu(self, connectSignal=True):
 406          # Actions
 407          self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction(
 408              _translate(
 409                  "MainWindow", "Move to Trash"), self.on_action_SentTrash)
 410          self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction(
 411              _translate(
 412                  "MainWindow", "Copy destination address to clipboard"),
 413              self.on_action_SentClipboard)
 414          self.actionForceSend = self.ui.sentContextMenuToolbar.addAction(
 415              _translate(
 416                  "MainWindow", "Force send"), self.on_action_ForceSend)
 417          self.actionSentReply = self.ui.sentContextMenuToolbar.addAction(
 418              _translate("MainWindow", "Send update"),
 419              self.on_action_SentReply)
 420          # self.popMenuSent = QtGui.QMenu( self )
 421          # self.popMenuSent.addAction( self.actionSentClipboard )
 422          # self.popMenuSent.addAction( self.actionTrashSentMessage )
 423  
 424      def rerenderTabTreeSubscriptions(self):
 425          treeWidget = self.ui.treeWidgetSubscriptions
 426          folders = list(Ui_FolderWidget.folderWeight.keys())
 427          folders.remove("new")
 428  
 429          # sort ascending when creating
 430          if treeWidget.topLevelItemCount() == 0:
 431              treeWidget.header().setSortIndicator(
 432                  0, QtCore.Qt.AscendingOrder)
 433          # init dictionary
 434  
 435          db = getSortedSubscriptions(True)
 436          for address in db:
 437              for folder in folders:
 438                  if folder not in db[address]:
 439                      db[address][folder] = {}
 440  
 441          if treeWidget.isSortingEnabled():
 442              treeWidget.setSortingEnabled(False)
 443  
 444          widgets = {}
 445          i = 0
 446          while i < treeWidget.topLevelItemCount():
 447              widget = treeWidget.topLevelItem(i)
 448              if widget is not None:
 449                  toAddress = widget.address
 450              else:
 451                  toAddress = None
 452  
 453              if toAddress not in db:
 454                  treeWidget.takeTopLevelItem(i)
 455                  # no increment
 456                  continue
 457              unread = 0
 458              j = 0
 459              while j < widget.childCount():
 460                  subwidget = widget.child(j)
 461                  try:
 462                      subwidget.setUnreadCount(db[toAddress][subwidget.folderName]['count'])
 463                      unread += db[toAddress][subwidget.folderName]['count']
 464                      db[toAddress].pop(subwidget.folderName, None)
 465                  except:
 466                      widget.takeChild(j)
 467                      # no increment
 468                      continue
 469                  j += 1
 470  
 471              # add missing folders
 472              if len(db[toAddress]) > 0:
 473                  j = 0
 474                  for f, c in db[toAddress].items():
 475                      try:
 476                          subwidget = Ui_FolderWidget(widget, j, toAddress, f, c['count'])
 477                      except KeyError:
 478                          subwidget = Ui_FolderWidget(widget, j, toAddress, f, 0)
 479                      j += 1
 480              widget.setUnreadCount(unread)
 481              db.pop(toAddress, None)
 482              i += 1
 483  
 484          i = 0
 485          for toAddress in db:
 486              widget = Ui_SubscriptionWidget(
 487                  treeWidget,
 488                  i,
 489                  toAddress,
 490                  db[toAddress]["inbox"]['count'],
 491                  db[toAddress]["inbox"]['label'],
 492                  db[toAddress]["inbox"]['enabled'])
 493              j = 0
 494              unread = 0
 495              for folder in folders:
 496                  try:
 497                      subwidget = Ui_FolderWidget(widget, j, toAddress, folder, db[toAddress][folder]['count'])
 498                      unread += db[toAddress][folder]['count']
 499                  except KeyError:
 500                      subwidget = Ui_FolderWidget(widget, j, toAddress, folder, 0)
 501                  j += 1
 502              widget.setUnreadCount(unread)
 503              i += 1
 504  
 505          treeWidget.setSortingEnabled(True)
 506  
 507      def rerenderTabTreeMessages(self):
 508          self.rerenderTabTree('messages')
 509  
 510      def rerenderTabTreeChans(self):
 511          self.rerenderTabTree('chan')
 512  
 513      def rerenderTabTree(self, tab):
 514          if tab == 'messages':
 515              treeWidget = self.ui.treeWidgetYourIdentities
 516          elif tab == 'chan':
 517              treeWidget = self.ui.treeWidgetChans
 518          folders = list(Ui_FolderWidget.folderWeight.keys())
 519  
 520          # sort ascending when creating
 521          if treeWidget.topLevelItemCount() == 0:
 522              treeWidget.header().setSortIndicator(
 523                  0, QtCore.Qt.AscendingOrder)
 524          # init dictionary
 525          db = {}
 526          enabled = {}
 527  
 528          for toAddress in config.addresses(True):
 529              isEnabled = config.getboolean(
 530                  toAddress, 'enabled')
 531              isChan = config.safeGetBoolean(
 532                  toAddress, 'chan')
 533              isMaillinglist = config.safeGetBoolean(
 534                  toAddress, 'mailinglist')
 535  
 536              if treeWidget == self.ui.treeWidgetYourIdentities:
 537                  if isChan:
 538                      continue
 539              elif treeWidget == self.ui.treeWidgetChans:
 540                  if not isChan:
 541                      continue
 542  
 543              db[toAddress] = {}
 544              for folder in folders:
 545                  db[toAddress][folder] = 0
 546  
 547              enabled[toAddress] = isEnabled
 548  
 549          # get number of (unread) messages
 550          total = 0
 551          queryreturn = sqlQuery(
 552              "SELECT toaddress, folder, count(msgid) as cnt "
 553              "FROM inbox "
 554              "WHERE read = 0 "
 555              "GROUP BY toaddress, folder")
 556          for row in queryreturn:
 557              toaddress, folder, cnt = row
 558              total += cnt
 559              if toaddress in db and folder in db[toaddress]:
 560                  db[toaddress][folder] = cnt
 561          if treeWidget == self.ui.treeWidgetYourIdentities:
 562              db[None] = {}
 563              db[None]["inbox"] = total
 564              db[None]["new"] = total
 565              db[None]["sent"] = 0
 566              db[None]["trash"] = 0
 567              enabled[None] = True
 568  
 569          if treeWidget.isSortingEnabled():
 570              treeWidget.setSortingEnabled(False)
 571  
 572          widgets = {}
 573          i = 0
 574          while i < treeWidget.topLevelItemCount():
 575              widget = treeWidget.topLevelItem(i)
 576              if widget is not None:
 577                  toAddress = widget.address
 578              else:
 579                  toAddress = None
 580  
 581              if toAddress not in db:
 582                  treeWidget.takeTopLevelItem(i)
 583                  # no increment
 584                  continue
 585              unread = 0
 586              j = 0
 587              while j < widget.childCount():
 588                  subwidget = widget.child(j)
 589                  try:
 590                      subwidget.setUnreadCount(
 591                          db[toAddress][subwidget.folderName])
 592                      if subwidget.folderName not in ("new", "trash", "sent"):
 593                          unread += db[toAddress][subwidget.folderName]
 594                      db[toAddress].pop(subwidget.folderName, None)
 595                  except:
 596                      widget.takeChild(j)
 597                      # no increment
 598                      continue
 599                  j += 1
 600  
 601              # add missing folders
 602              if len(db[toAddress]) > 0:
 603                  j = 0
 604                  for f, c in db[toAddress].items():
 605                      if toAddress is not None and tab == 'messages' and folder == "new":
 606                          continue
 607                      subwidget = Ui_FolderWidget(widget, j, toAddress, f, c)
 608                      if subwidget.folderName not in ("new", "trash", "sent"):
 609                          unread += c
 610                      j += 1
 611              widget.setUnreadCount(unread)
 612              db.pop(toAddress, None)
 613              i += 1
 614  
 615          i = 0
 616          for toAddress in db:
 617              widget = Ui_AddressWidget(treeWidget, i, toAddress, db[toAddress]["inbox"], enabled[toAddress])
 618              j = 0
 619              unread = 0
 620              for folder in folders:
 621                  if toAddress is not None and tab == 'messages' and folder == "new":
 622                      continue
 623                  subwidget = Ui_FolderWidget(widget, j, toAddress, folder, db[toAddress][folder])
 624                  if subwidget.folderName not in ("new", "trash", "sent"):
 625                      unread += db[toAddress][folder]
 626                  j += 1
 627              widget.setUnreadCount(unread)
 628              i += 1
 629  
 630          treeWidget.setSortingEnabled(True)
 631  
 632      def __init__(self, parent=None):
 633          QtGui.QWidget.__init__(self, parent)
 634          self.ui = Ui_MainWindow()
 635          self.ui.setupUi(self)
 636  
 637          self.qmytranslator = self.qsystranslator = None
 638          self.indicatorUpdate = None
 639          self.actionStatus = None
 640  
 641          # the last time that a message arrival sound was played
 642          self.lastSoundTime = datetime.now() - timedelta(days=1)
 643  
 644          # Ask the user if we may delete their old version 1 addresses if they
 645          # have any.
 646          for addressInKeysFile in config.addresses():
 647              status, addressVersionNumber, streamNumber, hash = decodeAddress(
 648                  addressInKeysFile)
 649              if addressVersionNumber == 1:
 650                  displayMsg = _translate(
 651                      "MainWindow",
 652                      "One of your addresses, %1, is an old version 1 address. "
 653                      "Version 1 addresses are no longer supported. "
 654                      "May we delete it now?").arg(addressInKeysFile)
 655                  reply = QtGui.QMessageBox.question(
 656                      self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
 657                  if reply == QtGui.QMessageBox.Yes:
 658                      config.remove_section(addressInKeysFile)
 659                      config.save()
 660  
 661          self.change_translation()
 662  
 663          # e.g. for editing labels
 664          self.recurDepth = 0
 665  
 666          # switch back to this when replying
 667          self.replyFromTab = None
 668  
 669          # so that quit won't loop
 670          self.wait = self.quitAccepted = False
 671  
 672          self.init_file_menu()
 673          self.init_inbox_popup_menu()
 674          self.init_identities_popup_menu()
 675          self.init_addressbook_popup_menu()
 676          self.init_subscriptions_popup_menu()
 677          self.init_chan_popup_menu()
 678          self.init_sent_popup_menu()
 679  
 680          # Initialize the user's list of addresses on the 'Chan' tab.
 681          self.rerenderTabTreeChans()
 682  
 683          # Initialize the user's list of addresses on the 'Messages' tab.
 684          self.rerenderTabTreeMessages()
 685  
 686          # Set welcome message
 687          self.ui.textEditInboxMessage.setText(_translate("MainWindow", """
 688          Welcome to easy and secure Bitmessage
 689              * send messages to other people
 690              * send broadcast messages like twitter or
 691              * discuss in chan(nel)s with other people
 692          """))
 693  
 694          # Initialize the address book
 695          self.rerenderAddressBook()
 696  
 697          # Initialize the Subscriptions
 698          self.rerenderSubscriptions()
 699  
 700          # Initialize the inbox search
 701          QtCore.QObject.connect(self.ui.inboxSearchLineEdit, QtCore.SIGNAL(
 702              "returnPressed()"), self.inboxSearchLineEditReturnPressed)
 703          QtCore.QObject.connect(self.ui.inboxSearchLineEditSubscriptions, QtCore.SIGNAL(
 704              "returnPressed()"), self.inboxSearchLineEditReturnPressed)
 705          QtCore.QObject.connect(self.ui.inboxSearchLineEditChans, QtCore.SIGNAL(
 706              "returnPressed()"), self.inboxSearchLineEditReturnPressed)
 707          QtCore.QObject.connect(self.ui.inboxSearchLineEdit, QtCore.SIGNAL(
 708              "textChanged(QString)"), self.inboxSearchLineEditUpdated)
 709          QtCore.QObject.connect(self.ui.inboxSearchLineEditSubscriptions, QtCore.SIGNAL(
 710              "textChanged(QString)"), self.inboxSearchLineEditUpdated)
 711          QtCore.QObject.connect(self.ui.inboxSearchLineEditChans, QtCore.SIGNAL(
 712              "textChanged(QString)"), self.inboxSearchLineEditUpdated)
 713  
 714          # Initialize addressbook
 715          QtCore.QObject.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL(
 716              "itemChanged(QTableWidgetItem *)"), self.tableWidgetAddressBookItemChanged)
 717          # This is necessary for the completer to work if multiple recipients
 718          QtCore.QObject.connect(self.ui.lineEditTo, QtCore.SIGNAL(
 719              "cursorPositionChanged(int, int)"), self.ui.lineEditTo.completer().onCursorPositionChanged)
 720  
 721          # show messages from message list
 722          QtCore.QObject.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL(
 723              "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked)
 724          QtCore.QObject.connect(self.ui.tableWidgetInboxSubscriptions, QtCore.SIGNAL(
 725              "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked)
 726          QtCore.QObject.connect(self.ui.tableWidgetInboxChans, QtCore.SIGNAL(
 727              "itemSelectionChanged ()"), self.tableWidgetInboxItemClicked)
 728  
 729          # tree address lists
 730          QtCore.QObject.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL(
 731              "itemSelectionChanged ()"), self.treeWidgetItemClicked)
 732          QtCore.QObject.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL(
 733              "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged)
 734          QtCore.QObject.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL(
 735              "itemSelectionChanged ()"), self.treeWidgetItemClicked)
 736          QtCore.QObject.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL(
 737              "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged)
 738          QtCore.QObject.connect(self.ui.treeWidgetChans, QtCore.SIGNAL(
 739              "itemSelectionChanged ()"), self.treeWidgetItemClicked)
 740          QtCore.QObject.connect(self.ui.treeWidgetChans, QtCore.SIGNAL(
 741              "itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged)
 742          QtCore.QObject.connect(
 743              self.ui.tabWidget, QtCore.SIGNAL("currentChanged(int)"),
 744              self.tabWidgetCurrentChanged
 745          )
 746  
 747          # Put the colored icon on the status bar
 748          # self.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png"))
 749          self.setStatusBar(BMStatusBar())
 750          self.statusbar = self.statusBar()
 751  
 752          self.pushButtonStatusIcon = QtGui.QPushButton(self)
 753          self.pushButtonStatusIcon.setText('')
 754          self.pushButtonStatusIcon.setIcon(
 755              QtGui.QIcon(':/newPrefix/images/redicon.png'))
 756          self.pushButtonStatusIcon.setFlat(True)
 757          self.statusbar.insertPermanentWidget(0, self.pushButtonStatusIcon)
 758          QtCore.QObject.connect(self.pushButtonStatusIcon, QtCore.SIGNAL(
 759              "clicked()"), self.click_pushButtonStatusIcon)
 760  
 761          self.unreadCount = 0
 762  
 763          # Set the icon sizes for the identicons
 764          identicon_size = 3 * 7
 765          self.ui.tableWidgetInbox.setIconSize(QtCore.QSize(identicon_size, identicon_size))
 766          self.ui.treeWidgetChans.setIconSize(QtCore.QSize(identicon_size, identicon_size))
 767          self.ui.treeWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size))
 768          self.ui.treeWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size))
 769          self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size))
 770  
 771          self.UISignalThread = UISignaler.get()
 772  
 773          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 774              "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
 775          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 776              "updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)
 777          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 778              "updateSentItemStatusByToAddress(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByToAddress)
 779          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 780              "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata)
 781          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 782              "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),
 783              self.displayNewInboxMessage)
 784          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 785              "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,"
 786              "PyQt_PyObject,PyQt_PyObject)"),
 787              self.displayNewSentMessage)
 788          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 789              "setStatusIcon(PyQt_PyObject)"), self.setStatusIcon)
 790          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 791              "changedInboxUnread(PyQt_PyObject)"), self.changedInboxUnread)
 792          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 793              "rerenderMessagelistFromLabels()"), self.rerenderMessagelistFromLabels)
 794          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 795              "rerenderMessgelistToLabels()"), self.rerenderMessagelistToLabels)
 796          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 797              "rerenderAddressBook()"), self.rerenderAddressBook)
 798          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 799              "rerenderSubscriptions()"), self.rerenderSubscriptions)
 800          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 801              "removeInboxRowByMsgid(PyQt_PyObject)"), self.removeInboxRowByMsgid)
 802          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 803              "newVersionAvailable(PyQt_PyObject)"), self.newVersionAvailable)
 804          QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
 805              "displayAlert(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayAlert)
 806          self.UISignalThread.start()
 807  
 808          # Key press in tree view
 809          self.ui.treeWidgetYourIdentities.keyPressEvent = self.treeWidgetKeyPressEvent
 810          self.ui.treeWidgetSubscriptions.keyPressEvent = self.treeWidgetKeyPressEvent
 811          self.ui.treeWidgetChans.keyPressEvent = self.treeWidgetKeyPressEvent
 812  
 813          # Key press in addressbook
 814          self.ui.tableWidgetAddressBook.keyPressEvent = self.addressbookKeyPressEvent
 815  
 816          # Key press in messagelist
 817          self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetKeyPressEvent
 818          self.ui.tableWidgetInboxSubscriptions.keyPressEvent = self.tableWidgetKeyPressEvent
 819          self.ui.tableWidgetInboxChans.keyPressEvent = self.tableWidgetKeyPressEvent
 820  
 821          # Key press in messageview
 822          self.ui.textEditInboxMessage.keyPressEvent = self.textEditKeyPressEvent
 823          self.ui.textEditInboxMessageSubscriptions.keyPressEvent = self.textEditKeyPressEvent
 824          self.ui.textEditInboxMessageChans.keyPressEvent = self.textEditKeyPressEvent
 825  
 826          # Below this point, it would be good if all of the necessary global data
 827          # structures were initialized.
 828  
 829          self.rerenderComboBoxSendFrom()
 830          self.rerenderComboBoxSendFromBroadcast()
 831  
 832          # Put the TTL slider in the correct spot
 833          TTL = config.getint('bitmessagesettings', 'ttl')
 834          if TTL < 3600: # an hour
 835              TTL = 3600
 836          elif TTL > 28*24*60*60: # 28 days
 837              TTL = 28*24*60*60
 838          self.ui.horizontalSliderTTL.setSliderPosition((TTL - 3600) ** (1/3.199))
 839          self.updateHumanFriendlyTTLDescription(TTL)
 840  
 841          QtCore.QObject.connect(self.ui.horizontalSliderTTL, QtCore.SIGNAL(
 842              "valueChanged(int)"), self.updateTTL)
 843  
 844          self.initSettings()
 845          self.resetNamecoinConnection()
 846          self.sqlInit()
 847          self.indicatorInit()
 848          self.notifierInit()
 849          self.updateStartOnLogon()
 850  
 851          self.ui.updateNetworkSwitchMenuLabel()
 852  
 853          self._firstrun = config.safeGetBoolean(
 854              'bitmessagesettings', 'dontconnect')
 855  
 856          self._contact_selected = None
 857  
 858      def getContactSelected(self):
 859          """Returns last selected contact once"""
 860          try:
 861              return self._contact_selected
 862          except AttributeError:
 863              pass
 864          finally:
 865              self._contact_selected = None
 866  
 867      def updateStartOnLogon(self):
 868          """
 869          Configure Bitmessage to start on startup (or remove the
 870          configuration) based on the setting in the keys.dat file
 871          """
 872          startonlogon = config.safeGetBoolean(
 873              'bitmessagesettings', 'startonlogon')
 874          if is_windows:  # Auto-startup for Windows
 875              RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
 876              settings = QtCore.QSettings(
 877                  RUN_PATH, QtCore.QSettings.NativeFormat)
 878              # In case the user moves the program and the registry entry is
 879              # no longer valid, this will delete the old registry entry.
 880              if startonlogon:
 881                  settings.setValue("PyBitmessage", sys.argv[0])
 882              else:
 883                  settings.remove("PyBitmessage")
 884          else:
 885              try:  # get desktop plugin if any
 886                  self.desktop = get_plugin('desktop')()
 887                  self.desktop.adjust_startonlogon(startonlogon)
 888              except (NameError, TypeError):
 889                  self.desktop = False
 890  
 891      def updateTTL(self, sliderPosition):
 892          TTL = int(sliderPosition ** 3.199 + 3600)
 893          self.updateHumanFriendlyTTLDescription(TTL)
 894          config.set('bitmessagesettings', 'ttl', str(TTL))
 895          config.save()
 896  
 897      def updateHumanFriendlyTTLDescription(self, TTL):
 898          numberOfHours = int(round(TTL / (60*60)))
 899          font = QtGui.QFont()
 900          stylesheet = ""
 901  
 902          if numberOfHours < 48:
 903              self.ui.labelHumanFriendlyTTLDescription.setText(
 904                  _translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfHours) +
 905                  ", " +
 906                  _translate("MainWindow", "not recommended for chans", None, QtCore.QCoreApplication.CodecForTr)
 907                  )
 908              stylesheet = "QLabel { color : red; }"
 909              font.setBold(True)
 910          else:
 911              numberOfDays = int(round(TTL / (24*60*60)))
 912              self.ui.labelHumanFriendlyTTLDescription.setText(
 913                  _translate(
 914                      "MainWindow",
 915                      "%n day(s)",
 916                      None,
 917                      QtCore.QCoreApplication.CodecForTr,
 918                      numberOfDays))
 919              font.setBold(False)
 920          self.ui.labelHumanFriendlyTTLDescription.setStyleSheet(stylesheet)
 921          self.ui.labelHumanFriendlyTTLDescription.setFont(font)
 922  
 923      # Show or hide the application window after clicking an item within the
 924      # tray icon or, on Windows, the try icon itself.
 925      def appIndicatorShowOrHideWindow(self):
 926          if not self.actionShow.isChecked():
 927              self.hide()
 928          else:
 929              self.show()
 930              self.setWindowState(
 931                  self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
 932              self.raise_()
 933              self.activateWindow()
 934  
 935      # show the application window
 936      def appIndicatorShow(self):
 937          if self.actionShow is None:
 938              return
 939          if not self.actionShow.isChecked():
 940              self.actionShow.setChecked(True)
 941          self.appIndicatorShowOrHideWindow()
 942  
 943      # unchecks the show item on the application indicator
 944      def appIndicatorHide(self):
 945          if self.actionShow is None:
 946              return
 947          if self.actionShow.isChecked():
 948              self.actionShow.setChecked(False)
 949              self.appIndicatorShowOrHideWindow()
 950  
 951      def appIndicatorSwitchQuietMode(self):
 952          config.set(
 953              'bitmessagesettings', 'showtraynotifications',
 954              str(not self.actionQuiet.isChecked())
 955          )
 956  
 957      # application indicator show or hide
 958      """# application indicator show or hide
 959      def appIndicatorShowBitmessage(self):
 960          #if self.actionShow == None:
 961          #    return
 962          print self.actionShow.isChecked()
 963          if not self.actionShow.isChecked():
 964              self.hide()
 965              #self.setWindowState(self.windowState() & QtCore.Qt.WindowMinimized)
 966          else:
 967              self.appIndicatorShowOrHideWindow()"""
 968  
 969      # Show the program window and select inbox tab
 970      def appIndicatorInbox(self, item=None):
 971          self.appIndicatorShow()
 972          # select inbox
 973          self.ui.tabWidget.setCurrentIndex(
 974              self.ui.tabWidget.indexOf(self.ui.inbox)
 975          )
 976          self.ui.treeWidgetYourIdentities.setCurrentItem(
 977              self.ui.treeWidgetYourIdentities.topLevelItem(0).child(0)
 978          )
 979  
 980          if item:
 981              self.ui.tableWidgetInbox.setCurrentItem(item)
 982              self.tableWidgetInboxItemClicked()
 983          else:
 984              self.ui.tableWidgetInbox.setCurrentCell(0, 0)
 985  
 986      # Show the program window and select send tab
 987      def appIndicatorSend(self):
 988          self.appIndicatorShow()
 989          self.ui.tabWidget.setCurrentIndex(
 990              self.ui.tabWidget.indexOf(self.ui.send)
 991          )
 992  
 993      # Show the program window and select subscriptions tab
 994      def appIndicatorSubscribe(self):
 995          self.appIndicatorShow()
 996          self.ui.tabWidget.setCurrentIndex(
 997              self.ui.tabWidget.indexOf(self.ui.subscriptions)
 998          )
 999  
1000      # Show the program window and select channels tab
1001      def appIndicatorChannel(self):
1002          self.appIndicatorShow()
1003          self.ui.tabWidget.setCurrentIndex(
1004              self.ui.tabWidget.indexOf(self.ui.chans)
1005          )
1006  
1007      def updateUnreadStatus(self, widget, row, msgid, unread=True):
1008          """
1009          Switch unread for item of msgid and related items in
1010          other STableWidgets "All Accounts" and "Chans"
1011          """
1012          status = widget.item(row, 0).unread
1013          if status != unread:
1014              return
1015  
1016          widgets = [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans]
1017          rrow = None
1018          try:
1019              widgets.remove(widget)
1020              related = widgets.pop()
1021          except ValueError:
1022              pass
1023          else:
1024              # maybe use instead:
1025              # rrow = related.row(msgid), msgid should be QTableWidgetItem
1026              # related = related.findItems(msgid, QtCore.Qt.MatchExactly),
1027              # returns an empty list
1028              for rrow in range(related.rowCount()):
1029                  if related.item(rrow, 3).data() == msgid:
1030                      break
1031  
1032          for col in range(widget.columnCount()):
1033              widget.item(row, col).setUnread(not status)
1034              if rrow:
1035                  related.item(rrow, col).setUnread(not status)
1036  
1037      # Here we need to update unread count for:
1038      # - all widgets if there is no args
1039      # - All accounts
1040      # - corresponding account if current is "All accounts"
1041      # - current account otherwise
1042      def propagateUnreadCount(self, folder=None, widget=None):
1043          queryReturn = sqlQuery(
1044              'SELECT toaddress, folder, COUNT(msgid) AS cnt'
1045              ' FROM inbox WHERE read = 0 GROUP BY toaddress, folder')
1046          totalUnread = {}
1047          normalUnread = {}
1048          broadcastsUnread = {}
1049          for addr, fld, count in queryReturn:
1050              try:
1051                  normalUnread[addr][fld] = count
1052              except KeyError:
1053                  normalUnread[addr] = {fld: count}
1054              try:
1055                  totalUnread[fld] += count
1056              except KeyError:
1057                  totalUnread[fld] = count
1058          if widget in (
1059                  self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans):
1060              widgets = (self.ui.treeWidgetYourIdentities,)
1061          else:
1062              widgets = (
1063                  self.ui.treeWidgetYourIdentities,
1064                  self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans
1065              )
1066              queryReturn = sqlQuery(
1067                  'SELECT fromaddress, folder, COUNT(msgid) AS cnt'
1068                  ' FROM inbox WHERE read = 0 AND toaddress = ?'
1069                  ' GROUP BY fromaddress, folder', str_broadcast_subscribers)
1070              for addr, fld, count in queryReturn:
1071                  try:
1072                      broadcastsUnread[addr][fld] = count
1073                  except KeyError:
1074                      broadcastsUnread[addr] = {fld: count}
1075  
1076          for treeWidget in widgets:
1077              root = treeWidget.invisibleRootItem()
1078              for i in range(root.childCount()):
1079                  addressItem = root.child(i)
1080                  if addressItem.type == AccountMixin.ALL:
1081                      newCount = sum(totalUnread.values())
1082                      self.drawTrayIcon(self.currentTrayIconFileName, newCount)
1083                  else:
1084                      try:
1085                          newCount = sum((
1086                              broadcastsUnread
1087                              if addressItem.type == AccountMixin.SUBSCRIPTION
1088                              else normalUnread
1089                          )[addressItem.address].values())
1090                      except KeyError:
1091                          newCount = 0
1092                  if newCount != addressItem.unreadCount:
1093                      addressItem.setUnreadCount(newCount)
1094                  for j in range(addressItem.childCount()):
1095                      folderItem = addressItem.child(j)
1096                      folderName = folderItem.folderName
1097                      if folderName == "new":
1098                          folderName = "inbox"
1099                      if folder and folderName != folder:
1100                          continue
1101                      if addressItem.type == AccountMixin.ALL:
1102                          newCount = totalUnread.get(folderName, 0)
1103                      else:
1104                          try:
1105                              newCount = (
1106                                  broadcastsUnread
1107                                  if addressItem.type == AccountMixin.SUBSCRIPTION
1108                                  else normalUnread
1109                              )[addressItem.address][folderName]
1110                          except KeyError:
1111                              newCount = 0
1112                      if newCount != folderItem.unreadCount:
1113                          folderItem.setUnreadCount(newCount)
1114  
1115      def addMessageListItem(self, tableWidget, items):
1116          sortingEnabled = tableWidget.isSortingEnabled()
1117          if sortingEnabled:
1118              tableWidget.setSortingEnabled(False)
1119          tableWidget.insertRow(0)
1120          for i, item in enumerate(items):
1121              tableWidget.setItem(0, i, item)
1122          if sortingEnabled:
1123              tableWidget.setSortingEnabled(True)
1124  
1125      def addMessageListItemSent(
1126          self, tableWidget, toAddress, fromAddress, subject,
1127          status, ackdata, lastactiontime
1128      ):
1129          acct = accountClass(fromAddress) or BMAccount(fromAddress)
1130          acct.parseMessage(toAddress, fromAddress, subject, "")
1131  
1132          if status == 'awaitingpubkey':
1133              statusText = _translate(
1134                  "MainWindow",
1135                  "Waiting for their encryption key. Will request it again soon."
1136              )
1137          elif status == 'doingpowforpubkey':
1138              statusText = _translate(
1139                  "MainWindow", "Doing work necessary to request encryption key."
1140              )
1141          elif status == 'msgqueued':
1142              statusText = _translate("MainWindow", "Queued.")
1143          elif status == 'msgsent':
1144              statusText = _translate(
1145                  "MainWindow",
1146                  "Message sent. Waiting for acknowledgement. Sent at %1"
1147              ).arg(l10n.formatTimestamp(lastactiontime))
1148          elif status == 'msgsentnoackexpected':
1149              statusText = _translate(
1150                  "MainWindow", "Message sent. Sent at %1"
1151              ).arg(l10n.formatTimestamp(lastactiontime))
1152          elif status == 'doingmsgpow':
1153              statusText = _translate(
1154                  "MainWindow", "Doing work necessary to send message.")
1155          elif status == 'ackreceived':
1156              statusText = _translate(
1157                  "MainWindow",
1158                  "Acknowledgement of the message received %1"
1159              ).arg(l10n.formatTimestamp(lastactiontime))
1160          elif status == 'broadcastqueued':
1161              statusText = _translate(
1162                  "MainWindow", "Broadcast queued.")
1163          elif status == 'doingbroadcastpow':
1164              statusText = _translate(
1165                  "MainWindow", "Doing work necessary to send broadcast.")
1166          elif status == 'broadcastsent':
1167              statusText = _translate("MainWindow", "Broadcast on %1").arg(
1168                  l10n.formatTimestamp(lastactiontime))
1169          elif status == 'toodifficult':
1170              statusText = _translate(
1171                  "MainWindow",
1172                  "Problem: The work demanded by the recipient is more"
1173                  " difficult than you are willing to do. %1"
1174              ).arg(l10n.formatTimestamp(lastactiontime))
1175          elif status == 'badkey':
1176              statusText = _translate(
1177                  "MainWindow",
1178                  "Problem: The recipient\'s encryption key is no good."
1179                  " Could not encrypt message. %1"
1180              ).arg(l10n.formatTimestamp(lastactiontime))
1181          elif status == 'forcepow':
1182              statusText = _translate(
1183                  "MainWindow",
1184                  "Forced difficulty override. Send should start soon.")
1185          else:
1186              statusText = _translate(
1187                  "MainWindow", "Unknown status: %1 %2").arg(status).arg(
1188                  l10n.formatTimestamp(lastactiontime))
1189  
1190          items = [
1191              MessageList_AddressWidget(
1192                  toAddress, str(acct.toLabel, 'utf-8')),
1193              MessageList_AddressWidget(
1194                  fromAddress, str(acct.fromLabel, 'utf-8')),
1195              MessageList_SubjectWidget(
1196                  str(subject), str(acct.subject, 'utf-8', 'replace')),
1197              MessageList_TimeWidget(
1198                  statusText, False, lastactiontime, ackdata)]
1199          self.addMessageListItem(tableWidget, items)
1200  
1201          return acct
1202  
1203      def addMessageListItemInbox(
1204          self, tableWidget, toAddress, fromAddress, subject,
1205          msgid, received, read
1206      ):
1207          if toAddress == str_broadcast_subscribers:
1208              acct = accountClass(fromAddress)
1209          else:
1210              acct = accountClass(toAddress) or accountClass(fromAddress)
1211          if acct is None:
1212              acct = BMAccount(fromAddress)
1213          acct.parseMessage(toAddress, fromAddress, subject, "")
1214  
1215          items = [
1216              MessageList_AddressWidget(
1217                  toAddress, str(acct.toLabel, 'utf-8'), not read),
1218              MessageList_AddressWidget(
1219                  fromAddress, str(acct.fromLabel, 'utf-8'), not read),
1220              MessageList_SubjectWidget(
1221                  str(subject), str(acct.subject, 'utf-8', 'replace'),
1222                  not read),
1223              MessageList_TimeWidget(
1224                  l10n.formatTimestamp(received), not read, received, msgid)
1225          ]
1226          self.addMessageListItem(tableWidget, items)
1227  
1228          return acct
1229  
1230      # Load Sent items from database
1231      def loadSent(self, tableWidget, account, where="", what=""):
1232          if tableWidget == self.ui.tableWidgetInboxSubscriptions:
1233              tableWidget.setColumnHidden(0, True)
1234              tableWidget.setColumnHidden(1, False)
1235              xAddress = 'toaddress'
1236          elif tableWidget == self.ui.tableWidgetInboxChans:
1237              tableWidget.setColumnHidden(0, False)
1238              tableWidget.setColumnHidden(1, True)
1239              xAddress = 'both'
1240          else:
1241              tableWidget.setColumnHidden(0, False)
1242              tableWidget.setColumnHidden(1, bool(account))
1243              xAddress = 'fromaddress'
1244  
1245          queryreturn = helper_search.search_sql(
1246              xAddress, account, "sent", where, what, False)
1247  
1248          for row in queryreturn:
1249              self.addMessageListItemSent(tableWidget, *row)
1250  
1251          tableWidget.horizontalHeader().setSortIndicator(
1252              3, QtCore.Qt.DescendingOrder)
1253          tableWidget.setSortingEnabled(True)
1254          tableWidget.horizontalHeaderItem(3).setText(
1255              _translate("MainWindow", "Sent"))
1256          tableWidget.setUpdatesEnabled(True)
1257  
1258      # Load messages from database file
1259      def loadMessagelist(
1260          self, tableWidget, account, folder="inbox", where="", what="",
1261          unreadOnly=False
1262      ):
1263          tableWidget.setUpdatesEnabled(False)
1264          tableWidget.setSortingEnabled(False)
1265          tableWidget.setRowCount(0)
1266  
1267          if folder == 'sent':
1268              self.loadSent(tableWidget, account, where, what)
1269              return
1270  
1271          if tableWidget == self.ui.tableWidgetInboxSubscriptions:
1272              xAddress = "fromaddress"
1273              if not what:
1274                  where = _translate("MainWindow", "To")
1275                  what = str_broadcast_subscribers
1276          else:
1277              xAddress = "toaddress"
1278          if account is not None:
1279              tableWidget.setColumnHidden(0, True)
1280              tableWidget.setColumnHidden(1, False)
1281          else:
1282              tableWidget.setColumnHidden(0, False)
1283              tableWidget.setColumnHidden(1, False)
1284  
1285          queryreturn = helper_search.search_sql(
1286              xAddress, account, folder, where, what, unreadOnly)
1287  
1288          for row in queryreturn:
1289              toAddress, fromAddress, subject, _, msgid, received, read = row
1290              self.addMessageListItemInbox(
1291                  tableWidget, toAddress, fromAddress, subject,
1292                  msgid, received, read)
1293  
1294          tableWidget.horizontalHeader().setSortIndicator(
1295              3, QtCore.Qt.DescendingOrder)
1296          tableWidget.setSortingEnabled(True)
1297          tableWidget.selectRow(0)
1298          tableWidget.horizontalHeaderItem(3).setText(
1299              _translate("MainWindow", "Received"))
1300          tableWidget.setUpdatesEnabled(True)
1301  
1302      # create application indicator
1303      def appIndicatorInit(self, app):
1304          self.initTrayIcon("can-icon-24px-red.png", app)
1305          traySignal = "activated(QSystemTrayIcon::ActivationReason)"
1306          QtCore.QObject.connect(self.tray, QtCore.SIGNAL(
1307              traySignal), self.__icon_activated)
1308  
1309          m = QtGui.QMenu()
1310  
1311          self.actionStatus = QtGui.QAction(_translate(
1312              "MainWindow", "Not Connected"), m, checkable=False)
1313          m.addAction(self.actionStatus)
1314  
1315          # separator
1316          actionSeparator = QtGui.QAction('', m, checkable=False)
1317          actionSeparator.setSeparator(True)
1318          m.addAction(actionSeparator)
1319  
1320          # show bitmessage
1321          self.actionShow = QtGui.QAction(_translate(
1322              "MainWindow", "Show Bitmessage"), m, checkable=True)
1323          self.actionShow.setChecked(not config.getboolean(
1324              'bitmessagesettings', 'startintray'))
1325          self.actionShow.triggered.connect(self.appIndicatorShowOrHideWindow)
1326          if not sys.platform[0:3] == 'win':
1327              m.addAction(self.actionShow)
1328  
1329          # quiet mode
1330          self.actionQuiet = QtGui.QAction(_translate(
1331              "MainWindow", "Quiet Mode"), m, checkable=True)
1332          self.actionQuiet.setChecked(not config.getboolean(
1333              'bitmessagesettings', 'showtraynotifications'))
1334          self.actionQuiet.triggered.connect(self.appIndicatorSwitchQuietMode)
1335          m.addAction(self.actionQuiet)
1336  
1337          # Send
1338          actionSend = QtGui.QAction(_translate(
1339              "MainWindow", "Send"), m, checkable=False)
1340          actionSend.triggered.connect(self.appIndicatorSend)
1341          m.addAction(actionSend)
1342  
1343          # Subscribe
1344          actionSubscribe = QtGui.QAction(_translate(
1345              "MainWindow", "Subscribe"), m, checkable=False)
1346          actionSubscribe.triggered.connect(self.appIndicatorSubscribe)
1347          m.addAction(actionSubscribe)
1348  
1349          # Channels
1350          actionSubscribe = QtGui.QAction(_translate(
1351              "MainWindow", "Channel"), m, checkable=False)
1352          actionSubscribe.triggered.connect(self.appIndicatorChannel)
1353          m.addAction(actionSubscribe)
1354  
1355          # separator
1356          actionSeparator = QtGui.QAction('', m, checkable=False)
1357          actionSeparator.setSeparator(True)
1358          m.addAction(actionSeparator)
1359  
1360          # Quit
1361          m.addAction(_translate(
1362              "MainWindow", "Quit"), self.quit)
1363  
1364          self.tray.setContextMenu(m)
1365          self.tray.show()
1366  
1367      # returns the number of unread messages and subscriptions
1368      def getUnread(self):
1369          counters = [0, 0]
1370  
1371          queryreturn = sqlQuery('''
1372          SELECT msgid, toaddress, read FROM inbox where folder='inbox'
1373          ''')
1374          for msgid, toAddress, read in queryreturn:
1375  
1376              if not read:
1377                  # increment the unread subscriptions if True (1)
1378                  # else messages (0)
1379                  counters[toAddress == str_broadcast_subscribers] += 1
1380  
1381          return counters
1382  
1383      # play a sound
1384      def playSound(self, category, label):
1385          # filename of the sound to be played
1386          soundFilename = None
1387  
1388          def _choose_ext(basename):
1389              for ext in sound.extensions:
1390                  if os.path.isfile(os.extsep.join([basename, ext])):
1391                      return os.extsep + ext
1392  
1393          # if the address had a known label in the address book
1394          if label:
1395              # Does a sound file exist for this particular contact?
1396              soundFilename = state.appdata + 'sounds/' + label
1397              ext = _choose_ext(soundFilename)
1398              if not ext:
1399                  category = sound.SOUND_KNOWN
1400                  soundFilename = None
1401  
1402          if soundFilename is None:
1403              # Avoid making sounds more frequently than the threshold.
1404              # This suppresses playing sounds repeatedly when there
1405              # are many new messages
1406              if not sound.is_connection_sound(category):
1407                  # elapsed time since the last sound was played
1408                  dt = datetime.now() - self.lastSoundTime
1409                  # suppress sounds which are more frequent than the threshold
1410                  if dt.total_seconds() < self.maxSoundFrequencySec:
1411                      return
1412  
1413              # the sound is for an address which exists in the address book
1414              if category is sound.SOUND_KNOWN:
1415                  soundFilename = state.appdata + 'sounds/known'
1416              # the sound is for an unknown address
1417              elif category is sound.SOUND_UNKNOWN:
1418                  soundFilename = state.appdata + 'sounds/unknown'
1419              # initial connection sound
1420              elif category is sound.SOUND_CONNECTED:
1421                  soundFilename = state.appdata + 'sounds/connected'
1422              # disconnected sound
1423              elif category is sound.SOUND_DISCONNECTED:
1424                  soundFilename = state.appdata + 'sounds/disconnected'
1425              # sound when the connection status becomes green
1426              elif category is sound.SOUND_CONNECTION_GREEN:
1427                  soundFilename = state.appdata + 'sounds/green'
1428  
1429          if soundFilename is None:
1430              logger.warning("Probably wrong category number in playSound()")
1431              return
1432  
1433          if not sound.is_connection_sound(category):
1434              # record the last time that a received message sound was played
1435              self.lastSoundTime = datetime.now()
1436  
1437          try:  # try already known format
1438              soundFilename += ext
1439          except (TypeError, NameError):
1440              ext = _choose_ext(soundFilename)
1441              if not ext:
1442                  try:  # if no user sound file found try to play from theme
1443                      return self._theme_player(category, label)
1444                  except TypeError:
1445                      return
1446  
1447              soundFilename += ext
1448  
1449          self._player(soundFilename)
1450  
1451      # Adapters and converters for QT <-> sqlite
1452      def sqlInit(self):
1453          register_adapter(QtCore.QByteArray, str)
1454  
1455      def indicatorInit(self):
1456          """
1457          Try init the distro specific appindicator,
1458          for example the Ubuntu MessagingMenu
1459          """
1460          def _noop_update(*args, **kwargs):
1461              pass
1462  
1463          try:
1464              self.indicatorUpdate = get_plugin('indicator')(self)
1465          except (NameError, TypeError):
1466              logger.warning("No indicator plugin found")
1467              self.indicatorUpdate = _noop_update
1468  
1469      # initialise the message notifier
1470      def notifierInit(self):
1471          def _simple_notify(
1472                  title, subtitle, category, label=None, icon=None):
1473              self.tray.showMessage(title, subtitle, 1, 2000)
1474  
1475          self._notifier = _simple_notify
1476          # does nothing if isAvailable returns false
1477          self._player = QtGui.QSound.play
1478  
1479          if not get_plugins:
1480              return
1481  
1482          _plugin = get_plugin('notification.message')
1483          if _plugin:
1484              self._notifier = _plugin
1485          else:
1486              logger.warning("No notification.message plugin found")
1487  
1488          self._theme_player = get_plugin('notification.sound', 'theme')
1489  
1490          if not QtGui.QSound.isAvailable():
1491              _plugin = get_plugin(
1492                  'notification.sound', 'file', fallback='file.fallback')
1493              if _plugin:
1494                  self._player = _plugin
1495              else:
1496                  logger.warning("No notification.sound plugin found")
1497  
1498      def notifierShow(
1499              self, title, subtitle, category, label=None, icon=None):
1500          self.playSound(category, label)
1501          self._notifier(
1502              str(title), str(subtitle), category, label, icon)
1503  
1504      # tree
1505      def treeWidgetKeyPressEvent(self, event):
1506          return self.handleKeyPress(event, self.getCurrentTreeWidget())
1507  
1508      # addressbook
1509      def addressbookKeyPressEvent(self, event):
1510          """Handle keypress event in addressbook widget"""
1511          if event.key() == QtCore.Qt.Key_Delete:
1512              self.on_action_AddressBookDelete()
1513          else:
1514              return QtGui.QTableWidget.keyPressEvent(
1515                  self.ui.tableWidgetAddressBook, event)
1516  
1517      # inbox / sent
1518      def tableWidgetKeyPressEvent(self, event):
1519          return self.handleKeyPress(event, self.getCurrentMessagelist())
1520  
1521      # messageview
1522      def textEditKeyPressEvent(self, event):
1523          return self.handleKeyPress(event, self.getCurrentMessageTextedit())
1524  
1525      def handleKeyPress(self, event, focus=None):
1526          """This method handles keypress events for all widgets on MyForm"""
1527          messagelist = self.getCurrentMessagelist()
1528          if event.key() == QtCore.Qt.Key_Delete:
1529              if isinstance(focus, (MessageView, QtGui.QTableWidget)):
1530                  folder = self.getCurrentFolder()
1531                  if folder == "sent":
1532                      self.on_action_SentTrash()
1533                  else:
1534                      self.on_action_InboxTrash()
1535              event.ignore()
1536          elif QtGui.QApplication.queryKeyboardModifiers() == QtCore.Qt.NoModifier:
1537              if event.key() == QtCore.Qt.Key_N:
1538                  currentRow = messagelist.currentRow()
1539                  if currentRow < messagelist.rowCount() - 1:
1540                      messagelist.selectRow(currentRow + 1)
1541                  event.ignore()
1542              elif event.key() == QtCore.Qt.Key_P:
1543                  currentRow = messagelist.currentRow()
1544                  if currentRow > 0:
1545                      messagelist.selectRow(currentRow - 1)
1546                  event.ignore()
1547              elif event.key() == QtCore.Qt.Key_R:
1548                  if messagelist == self.ui.tableWidgetInboxChans:
1549                      self.on_action_InboxReplyChan()
1550                  else:
1551                      self.on_action_InboxReply()
1552                  event.ignore()
1553              elif event.key() == QtCore.Qt.Key_C:
1554                  currentAddress = self.getCurrentAccount()
1555                  if currentAddress:
1556                      self.setSendFromComboBox(currentAddress)
1557                  self.ui.tabWidgetSend.setCurrentIndex(
1558                      self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
1559                  )
1560                  self.ui.tabWidget.setCurrentIndex(
1561                      self.ui.tabWidget.indexOf(self.ui.send)
1562                  )
1563                  self.ui.lineEditTo.setFocus()
1564                  event.ignore()
1565              elif event.key() == QtCore.Qt.Key_F:
1566                  try:
1567                      self.getCurrentSearchLine(retObj=True).setFocus()
1568                  except AttributeError:
1569                      pass
1570                  event.ignore()
1571          if not event.isAccepted():
1572              return
1573          if isinstance(focus, MessageView):
1574              return MessageView.keyPressEvent(focus, event)
1575          if isinstance(focus, QtGui.QTableWidget):
1576              return QtGui.QTableWidget.keyPressEvent(focus, event)
1577          if isinstance(focus, QtGui.QTreeWidget):
1578              return QtGui.QTreeWidget.keyPressEvent(focus, event)
1579  
1580      # menu button 'manage keys'
1581      def click_actionManageKeys(self):
1582          if 'darwin' in sys.platform or 'linux' in sys.platform:
1583              if state.appdata == '':
1584                  # reply = QtGui.QMessageBox.information(self, 'keys.dat?','You
1585                  # may manage your keys by editing the keys.dat file stored in
1586                  # the same directory as this program. It is important that you
1587                  # back up this file.', QMessageBox.Ok)
1588                  reply = QtGui.QMessageBox.information(
1589                      self,
1590                      'keys.dat?',
1591                      _translate(
1592                          "MainWindow",
1593                          "You may manage your keys by editing the keys.dat file stored in the same directory"
1594                          "as this program. It is important that you back up this file."
1595                      ),
1596                      QtGui.QMessageBox.Ok)
1597  
1598              else:
1599                  QtGui.QMessageBox.information(
1600                      self,
1601                      'keys.dat?',
1602                      _translate(
1603                          "MainWindow",
1604                          "You may manage your keys by editing the keys.dat file stored in"
1605                          "\n %1 \n"
1606                          "It is important that you back up this file."
1607                      ).arg(state.appdata),
1608                      QtGui.QMessageBox.Ok)
1609          elif sys.platform == 'win32' or sys.platform == 'win64':
1610              if state.appdata == '':
1611                  reply = QtGui.QMessageBox.question(
1612                      self,
1613                      _translate("MainWindow", "Open keys.dat?"),
1614                      _translate(
1615                          "MainWindow",
1616                          "You may manage your keys by editing the keys.dat file stored in the same directory as "
1617                          "this program. It is important that you back up this file. "
1618                          "Would you like to open the file now? "
1619                          "(Be sure to close Bitmessage before making any changes.)"),
1620                      QtGui.QMessageBox.Yes,
1621                      QtGui.QMessageBox.No)
1622              else:
1623                  reply = QtGui.QMessageBox.question(
1624                      self,
1625                      _translate("MainWindow", "Open keys.dat?"),
1626                      _translate(
1627                          "MainWindow",
1628                          "You may manage your keys by editing the keys.dat file stored in\n %1 \n"
1629                          "It is important that you back up this file. Would you like to open the file now?"
1630                          "(Be sure to close Bitmessage before making any changes.)").arg(state.appdata),
1631                      QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
1632              if reply == QtGui.QMessageBox.Yes:
1633                  openKeysFile()
1634  
1635      # menu button 'delete all treshed messages'
1636      def click_actionDeleteAllTrashedMessages(self):
1637          if QtGui.QMessageBox.question(
1638                  self,
1639                  _translate("MainWindow", "Delete trash?"),
1640                  _translate("MainWindow", "Are you sure you want to delete all trashed messages?"),
1641                  QtGui.QMessageBox.Yes,
1642                  QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
1643              return
1644          sqlStoredProcedure('deleteandvacuume')
1645          self.rerenderTabTreeMessages()
1646          self.rerenderTabTreeSubscriptions()
1647          self.rerenderTabTreeChans()
1648          if self.getCurrentFolder(self.ui.treeWidgetYourIdentities) == "trash":
1649              self.loadMessagelist(
1650                  self.ui.tableWidgetInbox,
1651                  self.getCurrentAccount(self.ui.treeWidgetYourIdentities),
1652                  "trash")
1653          elif self.getCurrentFolder(self.ui.treeWidgetSubscriptions) == "trash":
1654              self.loadMessagelist(
1655                  self.ui.tableWidgetInboxSubscriptions,
1656                  self.getCurrentAccount(self.ui.treeWidgetSubscriptions),
1657                  "trash")
1658          elif self.getCurrentFolder(self.ui.treeWidgetChans) == "trash":
1659              self.loadMessagelist(
1660                  self.ui.tableWidgetInboxChans,
1661                  self.getCurrentAccount(self.ui.treeWidgetChans),
1662                  "trash")
1663  
1664      # menu button 'regenerate deterministic addresses'
1665      def click_actionRegenerateDeterministicAddresses(self):
1666          dialog = dialogs.RegenerateAddressesDialog(self)
1667          if dialog.exec_():
1668              if dialog.lineEditPassphrase.text() == "":
1669                  QtGui.QMessageBox.about(
1670                      self, _translate("MainWindow", "bad passphrase"),
1671                      _translate(
1672                          "MainWindow",
1673                          "You must type your passphrase. If you don\'t"
1674                          " have one then this is not the form for you."
1675                      ))
1676                  return
1677              streamNumberForAddress = int(dialog.lineEditStreamNumber.text())
1678              try:
1679                  addressVersionNumber = int(
1680                      dialog.lineEditAddressVersionNumber.text())
1681              except:
1682                  QtGui.QMessageBox.about(
1683                      self,
1684                      _translate("MainWindow", "Bad address version number"),
1685                      _translate(
1686                          "MainWindow",
1687                          "Your address version number must be a number:"
1688                          " either 3 or 4."
1689                      ))
1690                  return
1691              if addressVersionNumber < 3 or addressVersionNumber > 4:
1692                  QtGui.QMessageBox.about(
1693                      self,
1694                      _translate("MainWindow", "Bad address version number"),
1695                      _translate(
1696                          "MainWindow",
1697                          "Your address version number must be either 3 or 4."
1698                      ))
1699                  return
1700              queues.addressGeneratorQueue.put((
1701                  'createDeterministicAddresses',
1702                  addressVersionNumber, streamNumberForAddress,
1703                  "regenerated deterministic address",
1704                  dialog.spinBoxNumberOfAddressesToMake.value(),
1705                  dialog.lineEditPassphrase.text().toUtf8(),
1706                  dialog.checkBoxEighteenByteRipe.isChecked()
1707              ))
1708              self.ui.tabWidget.setCurrentIndex(
1709                  self.ui.tabWidget.indexOf(self.ui.chans)
1710              )
1711  
1712      # opens 'join chan' dialog
1713      def click_actionJoinChan(self):
1714          dialogs.NewChanDialog(self)
1715  
1716      def showConnectDialog(self):
1717          dialog = dialogs.ConnectDialog(self)
1718          if dialog.exec_():
1719              if dialog.radioButtonConnectNow.isChecked():
1720                  self.ui.updateNetworkSwitchMenuLabel(False)
1721                  config.remove_option(
1722                      'bitmessagesettings', 'dontconnect')
1723                  config.save()
1724              elif dialog.radioButtonConfigureNetwork.isChecked():
1725                  self.click_actionSettings()
1726              else:
1727                  self._firstrun = False
1728  
1729      def showMigrationWizard(self, level):
1730          self.migrationWizardInstance = Ui_MigrationWizard(["a"])
1731          if self.migrationWizardInstance.exec_():
1732              pass
1733          else:
1734              pass
1735  
1736      def changeEvent(self, event):
1737          if event.type() == QtCore.QEvent.LanguageChange:
1738              self.ui.retranslateUi(self)
1739              self.init_inbox_popup_menu(False)
1740              self.init_identities_popup_menu(False)
1741              self.init_chan_popup_menu(False)
1742              self.init_addressbook_popup_menu(False)
1743              self.init_subscriptions_popup_menu(False)
1744              self.init_sent_popup_menu(False)
1745              self.ui.blackwhitelist.init_blacklist_popup_menu(False)
1746          if event.type() == QtCore.QEvent.WindowStateChange:
1747              if self.windowState() & QtCore.Qt.WindowMinimized:
1748                  if config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform:
1749                      QtCore.QTimer.singleShot(0, self.appIndicatorHide)
1750              elif event.oldState() & QtCore.Qt.WindowMinimized:
1751                  # The window state has just been changed to
1752                  # Normal/Maximised/FullScreen
1753                  pass
1754  
1755      def __icon_activated(self, reason):
1756          if reason == QtGui.QSystemTrayIcon.Trigger:
1757              self.actionShow.setChecked(not self.actionShow.isChecked())
1758              self.appIndicatorShowOrHideWindow()
1759  
1760      # Indicates whether or not there is a connection to the Bitmessage network
1761      connected = False
1762  
1763      def setStatusIcon(self, color):
1764          _notifications_enabled = not config.getboolean(
1765              'bitmessagesettings', 'hidetrayconnectionnotifications')
1766          if color not in ('red', 'yellow', 'green'):
1767              return
1768  
1769          self.pushButtonStatusIcon.setIcon(
1770              QtGui.QIcon(":/newPrefix/images/%sicon.png" % color))
1771          state.statusIconColor = color
1772          if color == 'red':
1773              # if the connection is lost then show a notification
1774              if self.connected and _notifications_enabled:
1775                  self.notifierShow(
1776                      'Bitmessage',
1777                      _translate("MainWindow", "Connection lost"),
1778                      sound.SOUND_DISCONNECTED)
1779              proxy = config.safeGet(
1780                  'bitmessagesettings', 'socksproxytype', 'none')
1781              if proxy == 'none' and not config.safeGetBoolean(
1782                      'bitmessagesettings', 'upnp'):
1783                  self.updateStatusBar(
1784                      _translate(
1785                          "MainWindow",
1786                          "Problems connecting? Try enabling UPnP in the Network"
1787                          " Settings"
1788                      ))
1789              elif proxy == 'SOCKS5' and config.safeGetBoolean(
1790                      'bitmessagesettings', 'onionservicesonly'):
1791                  self.updateStatusBar((
1792                      _translate(
1793                          "MainWindow",
1794                          "With recent tor you may never connect having"
1795                          " 'onionservicesonly' set in your config."), 1
1796                  ))
1797              self.connected = False
1798  
1799              if self.actionStatus is not None:
1800                  self.actionStatus.setText(_translate(
1801                      "MainWindow", "Not Connected"))
1802                  self.setTrayIconFile("can-icon-24px-red.png")
1803              return
1804  
1805          if self.statusbar.currentMessage() == (
1806              "Warning: You are currently not connected. Bitmessage will do"
1807              " the work necessary to send the message but it won't send"
1808              " until you connect."
1809          ):
1810              self.statusbar.clearMessage()
1811          # if a new connection has been established then show a notification
1812          if not self.connected and _notifications_enabled:
1813              self.notifierShow(
1814                  'Bitmessage',
1815                  _translate("MainWindow", "Connected"),
1816                  sound.SOUND_CONNECTED)
1817          self.connected = True
1818  
1819          if self.actionStatus is not None:
1820              self.actionStatus.setText(_translate(
1821                  "MainWindow", "Connected"))
1822              self.setTrayIconFile("can-icon-24px-%s.png" % color)
1823  
1824      def initTrayIcon(self, iconFileName, app):
1825          self.currentTrayIconFileName = iconFileName
1826          self.tray = QtGui.QSystemTrayIcon(
1827              self.calcTrayIcon(iconFileName, self.findInboxUnreadCount()), app)
1828  
1829      def setTrayIconFile(self, iconFileName):
1830          self.currentTrayIconFileName = iconFileName
1831          self.drawTrayIcon(iconFileName, self.findInboxUnreadCount())
1832  
1833      def calcTrayIcon(self, iconFileName, inboxUnreadCount):
1834          pixmap = QtGui.QPixmap(":/newPrefix/images/" + iconFileName)
1835          if inboxUnreadCount > 0:
1836              # choose font and calculate font parameters
1837              fontName = "Lucida"
1838              fontSize = 10
1839              font = QtGui.QFont(fontName, fontSize, QtGui.QFont.Bold)
1840              fontMetrics = QtGui.QFontMetrics(font)
1841              # text
1842              txt = str(inboxUnreadCount)
1843              rect = fontMetrics.boundingRect(txt)
1844              # margins that we add in the top-right corner
1845              marginX = 2
1846              # it looks like -2 is also ok due to the error of metric
1847              marginY = 0
1848              # if it renders too wide we need to change it to a plus symbol
1849              if rect.width() > 20:
1850                  txt = "+"
1851                  fontSize = 15
1852                  font = QtGui.QFont(fontName, fontSize, QtGui.QFont.Bold)
1853                  fontMetrics = QtGui.QFontMetrics(font)
1854                  rect = fontMetrics.boundingRect(txt)
1855              # draw text
1856              painter = QtGui.QPainter()
1857              painter.begin(pixmap)
1858              painter.setPen(
1859                  QtGui.QPen(QtGui.QColor(255, 0, 0), QtCore.Qt.SolidPattern))
1860              painter.setFont(font)
1861              painter.drawText(24-rect.right()-marginX, -rect.top()+marginY, txt)
1862              painter.end()
1863          return QtGui.QIcon(pixmap)
1864  
1865      def drawTrayIcon(self, iconFileName, inboxUnreadCount):
1866          self.tray.setIcon(self.calcTrayIcon(iconFileName, inboxUnreadCount))
1867  
1868      def changedInboxUnread(self, row=None):
1869          self.drawTrayIcon(
1870              self.currentTrayIconFileName, self.findInboxUnreadCount())
1871          self.rerenderTabTreeMessages()
1872          self.rerenderTabTreeSubscriptions()
1873          self.rerenderTabTreeChans()
1874  
1875      def findInboxUnreadCount(self, count=None):
1876          if count is None:
1877              queryreturn = sqlQuery('''SELECT count(*) from inbox WHERE folder='inbox' and read=0''')
1878              cnt = 0
1879              for row in queryreturn:
1880                  cnt, = row
1881              self.unreadCount = int(cnt)
1882          else:
1883              self.unreadCount = count
1884          return self.unreadCount
1885  
1886      def updateSentItemStatusByToAddress(self, toAddress, textToDisplay):
1887          for sent in (
1888              self.ui.tableWidgetInbox,
1889              self.ui.tableWidgetInboxSubscriptions,
1890              self.ui.tableWidgetInboxChans
1891          ):
1892              treeWidget = self.widgetConvert(sent)
1893              if self.getCurrentFolder(treeWidget) != "sent":
1894                  continue
1895              if treeWidget in (
1896                  self.ui.treeWidgetSubscriptions,
1897                  self.ui.treeWidgetChans
1898              ) and self.getCurrentAccount(treeWidget) != toAddress:
1899                  continue
1900  
1901              for i in range(sent.rowCount()):
1902                  rowAddress = sent.item(i, 0).data(QtCore.Qt.UserRole)
1903                  if toAddress == rowAddress:
1904                      sent.item(i, 3).setToolTip(textToDisplay)
1905                      try:
1906                          newlinePosition = textToDisplay.indexOf('\n')
1907                      except:
1908                          # If someone misses adding a "_translate" to a string before passing it to this function,
1909                          # this function won't receive a qstring which will cause an exception.
1910                          newlinePosition = 0
1911                      if newlinePosition > 1:
1912                          sent.item(i, 3).setText(
1913                              textToDisplay[:newlinePosition])
1914                      else:
1915                          sent.item(i, 3).setText(textToDisplay)
1916  
1917      def updateSentItemStatusByAckdata(self, ackdata, textToDisplay):
1918          if type(ackdata) is str:
1919              ackdata = QtCore.QByteArray(ackdata)
1920          for sent in (
1921              self.ui.tableWidgetInbox,
1922              self.ui.tableWidgetInboxSubscriptions,
1923              self.ui.tableWidgetInboxChans
1924          ):
1925              treeWidget = self.widgetConvert(sent)
1926              if self.getCurrentFolder(treeWidget) != "sent":
1927                  continue
1928              for i in range(sent.rowCount()):
1929                  toAddress = sent.item(i, 0).data(QtCore.Qt.UserRole)
1930                  tableAckdata = sent.item(i, 3).data()
1931                  status, addressVersionNumber, streamNumber, ripe = decodeAddress(
1932                      toAddress)
1933                  if ackdata == tableAckdata:
1934                      sent.item(i, 3).setToolTip(textToDisplay)
1935                      try:
1936                          newlinePosition = textToDisplay.indexOf('\n')
1937                      except:
1938                          # If someone misses adding a "_translate" to a string before passing it to this function, 
1939                          # this function won't receive a qstring which will cause an exception.
1940                          newlinePosition = 0
1941                      if newlinePosition > 1:
1942                          sent.item(i, 3).setText(
1943                              textToDisplay[:newlinePosition])
1944                      else:
1945                          sent.item(i, 3).setText(textToDisplay)
1946  
1947      def removeInboxRowByMsgid(self, msgid):
1948          # msgid and inventoryHash are the same thing
1949          for inbox in (
1950              self.ui.tableWidgetInbox,
1951              self.ui.tableWidgetInboxSubscriptions,
1952              self.ui.tableWidgetInboxChans
1953          ):
1954              i = None
1955              for i in range(inbox.rowCount()):
1956                  if msgid == inbox.item(i, 3).data():
1957                      break
1958              else:
1959                  continue
1960              self.updateStatusBar(_translate("MainWindow", "Message trashed"))
1961              treeWidget = self.widgetConvert(inbox)
1962              self.propagateUnreadCount(
1963                  # wrong assumption about current folder here:
1964                  self.getCurrentFolder(treeWidget), treeWidget
1965              )
1966              if i:
1967                  inbox.removeRow(i)
1968  
1969      def newVersionAvailable(self, version):
1970          self.notifiedNewVersion = ".".join(str(n) for n in version)
1971          self.updateStatusBar(_translate(
1972              "MainWindow",
1973              "New version of PyBitmessage is available: %1. Download it"
1974              " from https://github.com/Bitmessage/PyBitmessage/releases/latest"
1975              ).arg(self.notifiedNewVersion)
1976          )
1977  
1978      def displayAlert(self, title, text, exitAfterUserClicksOk):
1979          self.updateStatusBar(text)
1980          QtGui.QMessageBox.critical(self, title, text, QtGui.QMessageBox.Ok)
1981          if exitAfterUserClicksOk:
1982              os._exit(0)
1983  
1984      def rerenderMessagelistFromLabels(self):
1985          for messagelist in (self.ui.tableWidgetInbox,
1986                              self.ui.tableWidgetInboxChans,
1987                              self.ui.tableWidgetInboxSubscriptions):
1988              for i in range(messagelist.rowCount()):
1989                  messagelist.item(i, 1).setLabel()
1990  
1991      def rerenderMessagelistToLabels(self):
1992          for messagelist in (self.ui.tableWidgetInbox,
1993                              self.ui.tableWidgetInboxChans,
1994                              self.ui.tableWidgetInboxSubscriptions):
1995              for i in range(messagelist.rowCount()):
1996                  messagelist.item(i, 0).setLabel()
1997  
1998      def rerenderAddressBook(self):
1999          def addRow (address, label, type):
2000              self.ui.tableWidgetAddressBook.insertRow(0)
2001              newItem = Ui_AddressBookWidgetItemLabel(address, str(label, 'utf-8'), type)
2002              self.ui.tableWidgetAddressBook.setItem(0, 0, newItem)
2003              newItem = Ui_AddressBookWidgetItemAddress(address, str(label, 'utf-8'), type)
2004              self.ui.tableWidgetAddressBook.setItem(0, 1, newItem)
2005  
2006          oldRows = {}
2007          for i in range(self.ui.tableWidgetAddressBook.rowCount()):
2008              item = self.ui.tableWidgetAddressBook.item(i, 0)
2009              oldRows[item.address] = [item.label, item.type, i]
2010  
2011          if self.ui.tableWidgetAddressBook.rowCount() == 0:
2012              self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder)
2013          if self.ui.tableWidgetAddressBook.isSortingEnabled():
2014              self.ui.tableWidgetAddressBook.setSortingEnabled(False)
2015  
2016          newRows = {}
2017          # subscriptions
2018          queryreturn = sqlQuery('SELECT label, address FROM subscriptions WHERE enabled = 1')
2019          for row in queryreturn:
2020              label, address = row
2021              newRows[address] = [label, AccountMixin.SUBSCRIPTION]
2022          # chans
2023          for address in config.addresses(True):
2024              account = accountClass(address)
2025              if (account.type == AccountMixin.CHAN and config.safeGetBoolean(address, 'enabled')):
2026                  newRows[address] = [account.getLabel(), AccountMixin.CHAN]
2027          # normal accounts
2028          queryreturn = sqlQuery('SELECT * FROM addressbook')
2029          for row in queryreturn:
2030              label, address = row
2031              newRows[address] = [label, AccountMixin.NORMAL]
2032  
2033          completerList = []
2034          for address in sorted(
2035              oldRows, key=lambda x: oldRows[x][2], reverse=True
2036          ):
2037              try:
2038                  completerList.append(
2039                      newRows.pop(address)[0] + " <" + address + ">")
2040              except KeyError:
2041                  self.ui.tableWidgetAddressBook.removeRow(oldRows[address][2])
2042          for address in newRows:
2043              addRow(address, newRows[address][0], newRows[address][1])
2044              completerList.append(str(newRows[address][0], encoding="UTF-8") + " <" + address + ">")
2045  
2046          # sort
2047          self.ui.tableWidgetAddressBook.sortByColumn(
2048              0, QtCore.Qt.AscendingOrder)
2049          self.ui.tableWidgetAddressBook.setSortingEnabled(True)
2050          self.ui.lineEditTo.completer().model().setStringList(completerList)
2051  
2052      def rerenderSubscriptions(self):
2053          self.rerenderTabTreeSubscriptions()
2054  
2055      def click_pushButtonTTL(self):
2056          QtGui.QMessageBox.information(
2057              self,
2058              'Time To Live',
2059              _translate(
2060                  "MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message.
2061                   The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement
2062                   ,it will resend the message automatically. The longer the Time-To-Live, the
2063                   more work your computer must do to send the message.
2064                   A Time-To-Live of four or five days is often appropriate."""),
2065              QtGui.QMessageBox.Ok)
2066  
2067      def click_pushButtonClear(self):
2068          self.ui.lineEditSubject.setText("")
2069          self.ui.lineEditTo.setText("")
2070          self.ui.textEditMessage.reset()
2071          self.ui.comboBoxSendFrom.setCurrentIndex(0)
2072  
2073      def click_pushButtonSend(self):
2074          encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2
2075  
2076          self.statusbar.clearMessage()
2077  
2078          if self.ui.tabWidgetSend.currentIndex() == \
2079                  self.ui.tabWidgetSend.indexOf(self.ui.sendDirect):
2080              # message to specific people
2081              sendMessageToPeople = True
2082              fromAddress = str(self.ui.comboBoxSendFrom.itemData(
2083                  self.ui.comboBoxSendFrom.currentIndex(),
2084                  QtCore.Qt.UserRole).toString())
2085              toAddresses = str(self.ui.lineEditTo.text().toUtf8())
2086              subject = str(self.ui.lineEditSubject.text().toUtf8())
2087              message = str(
2088                  self.ui.textEditMessage.document().toPlainText().toUtf8())
2089          else:
2090              # broadcast message
2091              sendMessageToPeople = False
2092              fromAddress = str(self.ui.comboBoxSendFromBroadcast.itemData(
2093                  self.ui.comboBoxSendFromBroadcast.currentIndex(),
2094                  QtCore.Qt.UserRole).toString())
2095              subject = str(self.ui.lineEditSubjectBroadcast.text().toUtf8())
2096              message = str(
2097                  self.ui.textEditMessageBroadcast.document().toPlainText().toUtf8())
2098          """
2099          The whole network message must fit in 2^18 bytes.
2100          Let's assume 500 bytes of overhead. If someone wants to get that
2101          too an exact number you are welcome to but I think that it would
2102          be a better use of time to support message continuation so that
2103          users can send messages of any length.
2104          """
2105          if len(message) > (2 ** 18 - 500):
2106              QtGui.QMessageBox.about(
2107                  self, _translate("MainWindow", "Message too long"),
2108                  _translate(
2109                      "MainWindow",
2110                      "The message that you are trying to send is too long"
2111                      " by %1 bytes. (The maximum is 261644 bytes). Please"
2112                      " cut it down before sending."
2113                  ).arg(len(message) - (2 ** 18 - 500)))
2114              return
2115  
2116          acct = accountClass(fromAddress)
2117  
2118          # To send a message to specific people (rather than broadcast)
2119          if sendMessageToPeople:
2120              toAddressesList = set([
2121                  s.strip() for s in toAddresses.replace(',', ';').split(';')
2122              ])
2123              # remove duplicate addresses. If the user has one address
2124              # with a BM- and the same address without the BM-, this will
2125              # not catch it. They'll send the message to the person twice.
2126              for toAddress in toAddressesList:
2127                  if toAddress != '':
2128                      # label plus address
2129                      if "<" in toAddress and ">" in toAddress:
2130                          toAddress = toAddress.split('<')[1].split('>')[0]
2131                      # email address
2132                      if toAddress.find("@") >= 0:
2133                          if isinstance(acct, GatewayAccount):
2134                              acct.createMessage(toAddress, fromAddress, subject, message)
2135                              subject = acct.subject
2136                              toAddress = acct.toAddress
2137                          else:
2138                              if QtGui.QMessageBox.question(
2139                                      self,
2140                                      "Sending an email?",
2141                                      _translate(
2142                                          "MainWindow",
2143                                          "You are trying to send an email instead of a bitmessage. "
2144                                          "This requires registering with a gateway. Attempt to register?"),
2145                                      QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes:
2146                                  continue
2147                              email = acct.getLabel()
2148                              if email[-14:] != "@mailchuck.com": # attempt register
2149                                  # 12 character random email address
2150                                  email = ''.join(
2151                                          random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12)
2152                                          ) + "@mailchuck.com"
2153                              acct = MailchuckAccount(fromAddress)
2154                              acct.register(email)
2155                              config.set(fromAddress, 'label', email)
2156                              config.set(fromAddress, 'gateway', 'mailchuck')
2157                              config.save()
2158                              self.updateStatusBar(_translate(
2159                                  "MainWindow",
2160                                  "Error: Your account wasn't registered at"
2161                                  " an email gateway. Sending registration"
2162                                  " now as %1, please wait for the registration"
2163                                  " to be processed before retrying sending."
2164                                  ).arg(email)
2165                              )
2166                              return
2167                      status, addressVersionNumber, streamNumber = decodeAddress(toAddress)[:3]
2168                      if status != 'success':
2169                          try:
2170                              toAddress = str(toAddress, 'utf-8', 'ignore')
2171                          except:
2172                              pass
2173                          logger.error('Error: Could not decode recipient address ' + toAddress + ':' + status)
2174  
2175                          if status == 'missingbm':
2176                              self.updateStatusBar(_translate(
2177                                  "MainWindow",
2178                                  "Error: Bitmessage addresses start with"
2179                                  " BM-   Please check the recipient address %1"
2180                                  ).arg(toAddress))
2181                          elif status == 'checksumfailed':
2182                              self.updateStatusBar(_translate(
2183                                  "MainWindow",
2184                                  "Error: The recipient address %1 is not"
2185                                  " typed or copied correctly. Please check it."
2186                                  ).arg(toAddress))
2187                          elif status == 'invalidcharacters':
2188                              self.updateStatusBar(_translate(
2189                                  "MainWindow",
2190                                  "Error: The recipient address %1 contains"
2191                                  " invalid characters. Please check it."
2192                                  ).arg(toAddress))
2193                          elif status == 'versiontoohigh':
2194                              self.updateStatusBar(_translate(
2195                                  "MainWindow",
2196                                  "Error: The version of the recipient address"
2197                                  " %1 is too high. Either you need to upgrade"
2198                                  " your Bitmessage software or your"
2199                                  " acquaintance is being clever."
2200                                  ).arg(toAddress))
2201                          elif status == 'ripetooshort':
2202                              self.updateStatusBar(_translate(
2203                                  "MainWindow",
2204                                  "Error: Some data encoded in the recipient"
2205                                  " address %1 is too short. There might be"
2206                                  " something wrong with the software of"
2207                                  " your acquaintance."
2208                                  ).arg(toAddress))
2209                          elif status == 'ripetoolong':
2210                              self.updateStatusBar(_translate(
2211                                  "MainWindow",
2212                                  "Error: Some data encoded in the recipient"
2213                                  " address %1 is too long. There might be"
2214                                  " something wrong with the software of"
2215                                  " your acquaintance."
2216                                  ).arg(toAddress))
2217                          elif status == 'varintmalformed':
2218                              self.updateStatusBar(_translate(
2219                                  "MainWindow",
2220                                  "Error: Some data encoded in the recipient"
2221                                  " address %1 is malformed. There might be"
2222                                  " something wrong with the software of"
2223                                  " your acquaintance."
2224                                  ).arg(toAddress))
2225                          else:
2226                              self.updateStatusBar(_translate(
2227                                  "MainWindow",
2228                                  "Error: Something is wrong with the"
2229                                  " recipient address %1."
2230                                  ).arg(toAddress))
2231                      elif fromAddress == '':
2232                          self.updateStatusBar(_translate(
2233                              "MainWindow",
2234                              "Error: You must specify a From address. If you"
2235                              " don\'t have one, go to the"
2236                              " \'Your Identities\' tab.")
2237                          )
2238                      else:
2239                          toAddress = addBMIfNotPresent(toAddress)
2240  
2241                          if addressVersionNumber > 4 or addressVersionNumber <= 1:
2242                              QtGui.QMessageBox.about(
2243                                  self,
2244                                  _translate("MainWindow", "Address version number"),
2245                                  _translate(
2246                                      "MainWindow",
2247                                      "Concerning the address %1, Bitmessage cannot understand address version numbers"
2248                                      " of %2. Perhaps upgrade Bitmessage to the latest version."
2249                                  ).arg(toAddress).arg(str(addressVersionNumber)))
2250                              continue
2251                          if streamNumber > 1 or streamNumber == 0:
2252                              QtGui.QMessageBox.about(
2253                                  self,
2254                                  _translate("MainWindow", "Stream number"),
2255                                  _translate(
2256                                      "MainWindow",
2257                                      "Concerning the address %1, Bitmessage cannot handle stream numbers of %2."
2258                                      " Perhaps upgrade Bitmessage to the latest version."
2259                                  ).arg(toAddress).arg(str(streamNumber)))
2260                              continue
2261                          self.statusbar.clearMessage()
2262                          if state.statusIconColor == 'red':
2263                              self.updateStatusBar(_translate(
2264                                  "MainWindow",
2265                                  "Warning: You are currently not connected."
2266                                  " Bitmessage will do the work necessary to"
2267                                  " send the message but it won\'t send until"
2268                                  " you connect.")
2269                              )
2270                          ackdata = helper_sent.insert(
2271                              toAddress=toAddress, fromAddress=fromAddress,
2272                              subject=subject, message=message, encoding=encoding)
2273                          toLabel = ''
2274                          queryreturn = sqlQuery('''select label from addressbook where address=?''',
2275                                                 toAddress)
2276                          if queryreturn != []:
2277                              for row in queryreturn:
2278                                  toLabel, = row
2279  
2280                          self.displayNewSentMessage(
2281                              toAddress, toLabel, fromAddress, subject, message, ackdata)
2282                          queues.workerQueue.put(('sendmessage', toAddress))
2283  
2284                          self.click_pushButtonClear()
2285                          if self.replyFromTab is not None:
2286                              self.ui.tabWidget.setCurrentIndex(self.replyFromTab)
2287                              self.replyFromTab = None
2288                          self.updateStatusBar(_translate(
2289                              "MainWindow", "Message queued."))
2290                          # self.ui.tableWidgetInbox.setCurrentCell(0, 0)
2291                  else:
2292                      self.updateStatusBar(_translate(
2293                          "MainWindow", "Your \'To\' field is empty."))
2294          else:  # User selected 'Broadcast'
2295              if fromAddress == '':
2296                  self.updateStatusBar(_translate(
2297                      "MainWindow",
2298                      "Error: You must specify a From address. If you don\'t"
2299                      " have one, go to the \'Your Identities\' tab."
2300                  ))
2301              else:
2302                  self.statusbar.clearMessage()
2303                  # We don't actually need the ackdata for acknowledgement since
2304                  # this is a broadcast message, but we can use it to update the
2305                  # user interface when the POW is done generating.
2306                  toAddress = str_broadcast_subscribers
2307  
2308                  # msgid. We don't know what this will be until the POW is done.
2309                  ackdata = helper_sent.insert(
2310                      fromAddress=fromAddress,
2311                      subject=subject, message=message,
2312                      status='broadcastqueued', encoding=encoding)
2313  
2314                  toLabel = str_broadcast_subscribers
2315  
2316                  self.displayNewSentMessage(
2317                      toAddress, toLabel, fromAddress, subject, message, ackdata)
2318  
2319                  queues.workerQueue.put(('sendbroadcast', ''))
2320  
2321                  self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0)
2322                  self.ui.lineEditSubjectBroadcast.setText('')
2323                  self.ui.textEditMessageBroadcast.reset()
2324                  self.ui.tabWidget.setCurrentIndex(
2325                      self.ui.tabWidget.indexOf(self.ui.send)
2326                  )
2327                  self.ui.tableWidgetInboxSubscriptions.setCurrentCell(0, 0)
2328                  self.updateStatusBar(_translate(
2329                      "MainWindow", "Broadcast queued."))
2330  
2331      def click_pushButtonLoadFromAddressBook(self):
2332          self.ui.tabWidget.setCurrentIndex(5)
2333          for i in range(4):
2334              time.sleep(0.1)
2335              self.statusbar.clearMessage()
2336              time.sleep(0.1)
2337              self.updateStatusBar(_translate(
2338                  "MainWindow",
2339                  "Right click one or more entries in your address book and"
2340                  " select \'Send message to this address\'."
2341              ))
2342  
2343      def click_pushButtonFetchNamecoinID(self):
2344          identities = str(self.ui.lineEditTo.text().toUtf8()).split(";")
2345          err, addr = self.namecoin.query(identities[-1].strip())
2346          if err is not None:
2347              self.updateStatusBar(
2348                  _translate("MainWindow", "Error: %1").arg(err))
2349          else:
2350              identities[-1] = addr
2351              self.ui.lineEditTo.setText("; ".join(identities))
2352              self.updateStatusBar(_translate(
2353                  "MainWindow", "Fetched address from namecoin identity."))
2354  
2355      def setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(self, address):
2356          # If this is a chan then don't let people broadcast because no one
2357          # should subscribe to chan addresses.
2358          self.ui.tabWidgetSend.setCurrentIndex(
2359              self.ui.tabWidgetSend.indexOf(
2360                  self.ui.sendBroadcast
2361                  if config.safeGetBoolean(str(address), 'mailinglist')
2362                  else self.ui.sendDirect
2363              ))
2364  
2365      def rerenderComboBoxSendFrom(self):
2366          self.ui.comboBoxSendFrom.clear()
2367          for addressInKeysFile in config.addresses(True):
2368              # I realize that this is poor programming practice but I don't care.
2369              # It's easier for others to read.
2370              isEnabled = config.getboolean(
2371                  addressInKeysFile, 'enabled')
2372              isMaillinglist = config.safeGetBoolean(addressInKeysFile, 'mailinglist')
2373              if isEnabled and not isMaillinglist:
2374                  label = str(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip()
2375                  if label == "":
2376                      label = addressInKeysFile
2377                  self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), label, addressInKeysFile)
2378  #        self.ui.comboBoxSendFrom.model().sort(1, Qt.AscendingOrder)
2379          for i in range(self.ui.comboBoxSendFrom.count()):
2380              address = str(self.ui.comboBoxSendFrom.itemData(
2381                  i, QtCore.Qt.UserRole).toString())
2382              self.ui.comboBoxSendFrom.setItemData(
2383                  i, AccountColor(address).accountColor(),
2384                  QtCore.Qt.ForegroundRole)
2385          self.ui.comboBoxSendFrom.insertItem(0, '', '')
2386          if(self.ui.comboBoxSendFrom.count() == 2):
2387              self.ui.comboBoxSendFrom.setCurrentIndex(1)
2388          else:
2389              self.ui.comboBoxSendFrom.setCurrentIndex(0)
2390  
2391      def rerenderComboBoxSendFromBroadcast(self):
2392          self.ui.comboBoxSendFromBroadcast.clear()
2393          for addressInKeysFile in config.addresses(True):
2394              isEnabled = config.getboolean(
2395                  addressInKeysFile, 'enabled')
2396              isChan = config.safeGetBoolean(addressInKeysFile, 'chan')
2397              if isEnabled and not isChan:
2398                  label = str(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip()
2399                  if label == "":
2400                      label = addressInKeysFile
2401                  self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), label, addressInKeysFile)
2402          for i in range(self.ui.comboBoxSendFromBroadcast.count()):
2403              address = str(self.ui.comboBoxSendFromBroadcast.itemData(
2404                  i, QtCore.Qt.UserRole).toString())
2405              self.ui.comboBoxSendFromBroadcast.setItemData(
2406                  i, AccountColor(address).accountColor(),
2407                  QtCore.Qt.ForegroundRole)
2408          self.ui.comboBoxSendFromBroadcast.insertItem(0, '', '')
2409          if(self.ui.comboBoxSendFromBroadcast.count() == 2):
2410              self.ui.comboBoxSendFromBroadcast.setCurrentIndex(1)
2411          else:
2412              self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0)
2413  
2414      # This function is called by the processmsg function when that function
2415      # receives a message to an address that is acting as a
2416      # pseudo-mailing-list. The message will be broadcast out. This function
2417      # puts the message on the 'Sent' tab.
2418      def displayNewSentMessage(
2419              self, toAddress, toLabel, fromAddress, subject,
2420              message, ackdata):
2421          acct = accountClass(fromAddress)
2422          acct.parseMessage(toAddress, fromAddress, subject, message)
2423          tab = -1
2424          for sent in (
2425              self.ui.tableWidgetInbox,
2426              self.ui.tableWidgetInboxSubscriptions,
2427              self.ui.tableWidgetInboxChans
2428          ):
2429              tab += 1
2430              if tab == 1:
2431                  tab = 2
2432              treeWidget = self.widgetConvert(sent)
2433              if self.getCurrentFolder(treeWidget) != "sent":
2434                  continue
2435              if treeWidget == self.ui.treeWidgetYourIdentities \
2436                  and self.getCurrentAccount(treeWidget) not in (
2437                      fromAddress, None, False):
2438                  continue
2439              elif treeWidget in (
2440                  self.ui.treeWidgetSubscriptions,
2441                  self.ui.treeWidgetChans
2442              ) and self.getCurrentAccount(treeWidget) != toAddress:
2443                  continue
2444              elif not helper_search.check_match(
2445                  toAddress, fromAddress, subject, message,
2446                  self.getCurrentSearchOption(tab),
2447                  self.getCurrentSearchLine(tab)
2448              ):
2449                  continue
2450  
2451              self.addMessageListItemSent(
2452                  sent, toAddress, fromAddress, subject,
2453                  "msgqueued", ackdata, time.time())
2454              self.getAccountTextedit(acct).setPlainText(message)
2455              sent.setCurrentCell(0, 0)
2456  
2457      def displayNewInboxMessage(
2458              self, inventoryHash, toAddress, fromAddress, subject, message):
2459          acct = accountClass(
2460              fromAddress if toAddress == str_broadcast_subscribers
2461              else toAddress
2462          )
2463          inbox = self.getAccountMessagelist(acct)
2464          ret = treeWidget = None
2465          tab = -1
2466          for treeWidget in (
2467              self.ui.treeWidgetYourIdentities,
2468              self.ui.treeWidgetSubscriptions,
2469              self.ui.treeWidgetChans
2470          ):
2471              tab += 1
2472              if tab == 1:
2473                  tab = 2
2474              if not helper_search.check_match(
2475                  toAddress, fromAddress, subject, message,
2476                  self.getCurrentSearchOption(tab),
2477                  self.getCurrentSearchLine(tab)
2478              ):
2479                  continue
2480              tableWidget = self.widgetConvert(treeWidget)
2481              current_account = self.getCurrentAccount(treeWidget)
2482              current_folder = self.getCurrentFolder(treeWidget)
2483              # pylint: disable=too-many-boolean-expressions
2484              if ((tableWidget == inbox
2485                   and current_account == acct.address
2486                   and current_folder in ("inbox", None))
2487                  or (treeWidget == self.ui.treeWidgetYourIdentities
2488                      and current_account is None
2489                      and current_folder in ("inbox", "new", None))):
2490                  ret = self.addMessageListItemInbox(
2491                      tableWidget, toAddress, fromAddress, subject,
2492                      inventoryHash, time.time(), False)
2493  
2494          if ret is None:
2495              acct.parseMessage(toAddress, fromAddress, subject, "")
2496          else:
2497              acct = ret
2498          self.propagateUnreadCount(widget=treeWidget if ret else None)
2499          if config.safeGetBoolean(
2500                  'bitmessagesettings', 'showtraynotifications'):
2501              self.notifierShow(
2502                  _translate("MainWindow", "New Message"),
2503                  _translate("MainWindow", "From %1").arg(
2504                      str(acct.fromLabel, 'utf-8')),
2505                  sound.SOUND_UNKNOWN
2506              )
2507          if self.getCurrentAccount() is not None and (
2508                  (self.getCurrentFolder(treeWidget) != "inbox"
2509                   and self.getCurrentFolder(treeWidget) is not None)
2510                  or self.getCurrentAccount(treeWidget) != acct.address):
2511              # Ubuntu should notify of new message irrespective of
2512              # whether it's in current message list or not
2513              self.indicatorUpdate(True, to_label=acct.toLabel)
2514  
2515          try:
2516              if acct.feedback != GatewayAccount.ALL_OK:
2517                  if acct.feedback == GatewayAccount.REGISTRATION_DENIED:
2518                      dialogs.EmailGatewayDialog(
2519                          self, config, acct).exec_()
2520                  # possible other branches?
2521          except AttributeError:
2522              pass
2523  
2524      def click_pushButtonAddAddressBook(self, dialog=None):
2525          if not dialog:
2526              dialog = dialogs.AddAddressDialog(self)
2527          dialog.exec_()
2528          try:
2529              address, label = dialog.data
2530          except AttributeError:
2531              return
2532  
2533          # First we must check to see if the address is already in the
2534          # address book. The user cannot add it again or else it will
2535          # cause problems when updating and deleting the entry.
2536          if shared.isAddressInMyAddressBook(address):
2537              self.updateStatusBar(_translate(
2538                  "MainWindow",
2539                  "Error: You cannot add the same address to your"
2540                  " address book twice. Try renaming the existing one"
2541                  " if you want."
2542              ))
2543              return
2544  
2545          if helper_addressbook.insert(label=label, address=address):
2546              self.rerenderMessagelistFromLabels()
2547              self.rerenderMessagelistToLabels()
2548              self.rerenderAddressBook()
2549          else:
2550              self.updateStatusBar(_translate(
2551                  "MainWindow",
2552                  "Error: You cannot add your own address in the address book."
2553              ))
2554  
2555      def addSubscription(self, address, label):
2556          # This should be handled outside of this function, for error displaying
2557          # and such, but it must also be checked here.
2558          if shared.isAddressInMySubscriptionsList(address):
2559              return
2560          # Add to database (perhaps this should be separated from the MyForm class)
2561          sqlExecute(
2562              '''INSERT INTO subscriptions VALUES (?,?,?)''',
2563              label, address, True
2564          )
2565          self.rerenderMessagelistFromLabels()
2566          shared.reloadBroadcastSendersForWhichImWatching()
2567          self.rerenderAddressBook()
2568          self.rerenderTabTreeSubscriptions()
2569  
2570      def click_pushButtonAddSubscription(self):
2571          dialog = dialogs.NewSubscriptionDialog(self)
2572          dialog.exec_()
2573          try:
2574              address, label = dialog.data
2575          except AttributeError:
2576              return
2577  
2578          # We must check to see if the address is already in the
2579          # subscriptions list. The user cannot add it again or else it
2580          # will cause problems when updating and deleting the entry.
2581          if shared.isAddressInMySubscriptionsList(address):
2582              self.updateStatusBar(_translate(
2583                  "MainWindow",
2584                  "Error: You cannot add the same address to your"
2585                  " subscriptions twice. Perhaps rename the existing one"
2586                  " if you want."
2587              ))
2588              return
2589  
2590          self.addSubscription(address, label)
2591          # Now, if the user wants to display old broadcasts, let's get
2592          # them out of the inventory and put them
2593          # to the objectProcessorQueue to be processed
2594          if dialog.checkBoxDisplayMessagesAlreadyInInventory.isChecked():
2595              for value in dialog.recent:
2596                  queues.objectProcessorQueue.put((
2597                      value.type, value.payload
2598                  ))
2599  
2600      def click_pushButtonStatusIcon(self):
2601          dialogs.IconGlossaryDialog(self, config=config).exec_()
2602  
2603      def click_actionHelp(self):
2604          dialogs.HelpDialog(self).exec_()
2605  
2606      def click_actionSupport(self):
2607          support.createSupportMessage(self)
2608  
2609      def click_actionAbout(self):
2610          dialogs.AboutDialog(self).exec_()
2611  
2612      def click_actionSettings(self):
2613          dialogs.SettingsDialog(self, firstrun=self._firstrun).exec_()
2614  
2615      def on_action_Send(self):
2616          """Send message to current selected address"""
2617          self.click_pushButtonClear()
2618          account_item = self.getCurrentItem()
2619          if not account_item:
2620              return
2621          self.ui.lineEditTo.setText(account_item.accountString())
2622          self.ui.tabWidget.setCurrentIndex(
2623              self.ui.tabWidget.indexOf(self.ui.send)
2624          )
2625  
2626      def on_action_SpecialAddressBehaviorDialog(self):
2627          """Show SpecialAddressBehaviorDialog"""
2628          dialogs.SpecialAddressBehaviorDialog(self, config)
2629  
2630      def on_action_EmailGatewayDialog(self):
2631          dialog = dialogs.EmailGatewayDialog(self, config=config)
2632          # For Modal dialogs
2633          dialog.exec_()
2634          try:
2635              acct = dialog.data
2636          except AttributeError:
2637              return
2638  
2639          # Only settings remain here
2640          acct.settings()
2641          for i in range(self.ui.comboBoxSendFrom.count()):
2642              if str(self.ui.comboBoxSendFrom.itemData(i).toPyObject()) \
2643                      == acct.fromAddress:
2644                  self.ui.comboBoxSendFrom.setCurrentIndex(i)
2645                  break
2646          else:
2647              self.ui.comboBoxSendFrom.setCurrentIndex(0)
2648  
2649          self.ui.lineEditTo.setText(acct.toAddress)
2650          self.ui.lineEditSubject.setText(acct.subject)
2651          self.ui.textEditMessage.setText(acct.message)
2652          self.ui.tabWidgetSend.setCurrentIndex(
2653              self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
2654          )
2655          self.ui.tabWidget.setCurrentIndex(
2656              self.ui.tabWidget.indexOf(self.ui.send)
2657          )
2658          self.ui.textEditMessage.setFocus()
2659  
2660      def on_action_MarkAllRead(self):
2661          if QtGui.QMessageBox.question(
2662                  self, "Marking all messages as read?",
2663                  _translate(
2664                      "MainWindow",
2665                      "Are you sure you would like to mark all messages read?"
2666                  ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
2667          ) != QtGui.QMessageBox.Yes:
2668              return
2669          tableWidget = self.getCurrentMessagelist()
2670  
2671          idCount = tableWidget.rowCount()
2672          if idCount == 0:
2673              return
2674  
2675          msgids = []
2676          for i in range(0, idCount):
2677              msgids.append(tableWidget.item(i, 3).data())
2678              for col in range(tableWidget.columnCount()):
2679                  tableWidget.item(i, col).setUnread(False)
2680  
2681          markread = sqlExecuteChunked(
2682              "UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0",
2683              idCount, *msgids
2684          )
2685  
2686          if markread > 0:
2687              self.propagateUnreadCount()
2688  
2689      def click_NewAddressDialog(self):
2690          dialogs.NewAddressDialog(self)
2691  
2692      def network_switch(self):
2693          dontconnect_option = not config.safeGetBoolean(
2694              'bitmessagesettings', 'dontconnect')
2695          reply = QtGui.QMessageBox.question(
2696              self, _translate("MainWindow", "Disconnecting")
2697              if dontconnect_option else _translate("MainWindow", "Connecting"),
2698              _translate(
2699                  "MainWindow",
2700                  "Bitmessage will now drop all connections. Are you sure?"
2701              ) if dontconnect_option else _translate(
2702                  "MainWindow",
2703                  "Bitmessage will now start connecting to network. Are you sure?"
2704              ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel,
2705              QtGui.QMessageBox.Cancel)
2706          if reply != QtGui.QMessageBox.Yes:
2707              return
2708          config.set(
2709              'bitmessagesettings', 'dontconnect', str(dontconnect_option))
2710          config.save()
2711          self.ui.updateNetworkSwitchMenuLabel(dontconnect_option)
2712  
2713          self.ui.pushButtonFetchNamecoinID.setHidden(
2714              dontconnect_option or self.namecoin.test()[0] == 'failed'
2715          )
2716  
2717      # Quit selected from menu or application indicator
2718      def quit(self):
2719          """Quit the bitmessageqt application"""
2720          if self.quitAccepted and not self.wait:
2721              return
2722  
2723          self.show()
2724          self.raise_()
2725          self.activateWindow()
2726  
2727          waitForPow = True
2728          waitForConnection = False
2729          waitForSync = False
2730  
2731          # C PoW currently doesn't support interrupting and OpenCL is untested
2732          if getPowType() == "python" and (powQueueSize() > 0 or pendingUpload() > 0):
2733              reply = QtGui.QMessageBox.question(
2734                  self, _translate("MainWindow", "Proof of work pending"),
2735                  _translate(
2736                      "MainWindow",
2737                      "%n object(s) pending proof of work", None,
2738                      QtCore.QCoreApplication.CodecForTr, powQueueSize()
2739                  ) + ", " +
2740                  _translate(
2741                      "MainWindow",
2742                      "%n object(s) waiting to be distributed", None,
2743                      QtCore.QCoreApplication.CodecForTr, pendingUpload()
2744                  ) + "\n\n" +
2745                  _translate(
2746                      "MainWindow", "Wait until these tasks finish?"),
2747                  QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
2748                  | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
2749              if reply == QtGui.QMessageBox.No:
2750                  waitForPow = False
2751              elif reply == QtGui.QMessageBox.Cancel:
2752                  return
2753  
2754          if pendingDownload() > 0:
2755              reply = QtGui.QMessageBox.question(
2756                  self, _translate("MainWindow", "Synchronisation pending"),
2757                  _translate(
2758                      "MainWindow",
2759                      "Bitmessage hasn't synchronised with the network,"
2760                      " %n object(s) to be downloaded. If you quit now,"
2761                      " it may cause delivery delays. Wait until the"
2762                      " synchronisation finishes?", None,
2763                      QtCore.QCoreApplication.CodecForTr, pendingDownload()
2764                  ),
2765                  QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
2766                  | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
2767              if reply == QtGui.QMessageBox.Yes:
2768                  self.wait = waitForSync = True
2769              elif reply == QtGui.QMessageBox.Cancel:
2770                  return
2771  
2772          if state.statusIconColor == 'red' and not config.safeGetBoolean(
2773                  'bitmessagesettings', 'dontconnect'):
2774              reply = QtGui.QMessageBox.question(
2775                  self, _translate("MainWindow", "Not connected"),
2776                  _translate(
2777                      "MainWindow",
2778                      "Bitmessage isn't connected to the network. If you"
2779                      " quit now, it may cause delivery delays. Wait until"
2780                      " connected and the synchronisation finishes?"
2781                  ),
2782                  QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
2783                  | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
2784              if reply == QtGui.QMessageBox.Yes:
2785                  waitForConnection = True
2786                  self.wait = waitForSync = True
2787              elif reply == QtGui.QMessageBox.Cancel:
2788                  return
2789  
2790          self.quitAccepted = True
2791  
2792          self.updateStatusBar(_translate(
2793              "MainWindow", "Shutting down PyBitmessage... %1%").arg(0))
2794  
2795          if waitForConnection:
2796              self.updateStatusBar(_translate(
2797                  "MainWindow", "Waiting for network connection..."))
2798              while state.statusIconColor == 'red':
2799                  time.sleep(0.5)
2800                  QtCore.QCoreApplication.processEvents(
2801                      QtCore.QEventLoop.AllEvents, 1000
2802                  )
2803  
2804          # this probably will not work correctly, because there is a delay
2805          # between the status icon turning red and inventory exchange,
2806          # but it's better than nothing.
2807          if waitForSync:
2808              self.updateStatusBar(_translate(
2809                  "MainWindow", "Waiting for finishing synchronisation..."))
2810              while pendingDownload() > 0:
2811                  time.sleep(0.5)
2812                  QtCore.QCoreApplication.processEvents(
2813                      QtCore.QEventLoop.AllEvents, 1000
2814                  )
2815  
2816          if waitForPow:
2817              # check if PoW queue empty
2818              maxWorkerQueue = 0
2819              curWorkerQueue = powQueueSize()
2820              while curWorkerQueue > 0:
2821                  # worker queue size
2822                  curWorkerQueue = powQueueSize()
2823                  if curWorkerQueue > maxWorkerQueue:
2824                      maxWorkerQueue = curWorkerQueue
2825                  if curWorkerQueue > 0:
2826                      self.updateStatusBar(_translate(
2827                          "MainWindow", "Waiting for PoW to finish... %1%"
2828                      ).arg(50 * (maxWorkerQueue - curWorkerQueue) /
2829                            maxWorkerQueue))
2830                      time.sleep(0.5)
2831                      QtCore.QCoreApplication.processEvents(
2832                          QtCore.QEventLoop.AllEvents, 1000
2833                      )
2834  
2835              self.updateStatusBar(_translate(
2836                  "MainWindow", "Shutting down Pybitmessage... %1%").arg(50))
2837  
2838              QtCore.QCoreApplication.processEvents(
2839                  QtCore.QEventLoop.AllEvents, 1000
2840              )
2841              if maxWorkerQueue > 0:
2842                  # a bit of time so that the hashHolder is populated
2843                  time.sleep(0.5)
2844              QtCore.QCoreApplication.processEvents(
2845                  QtCore.QEventLoop.AllEvents, 1000
2846              )
2847  
2848              # check if upload (of objects created locally) pending
2849              self.updateStatusBar(_translate(
2850                  "MainWindow", "Waiting for objects to be sent... %1%").arg(50))
2851              maxPendingUpload = max(1, pendingUpload())
2852  
2853              while pendingUpload() > 1:
2854                  self.updateStatusBar(_translate(
2855                      "MainWindow",
2856                      "Waiting for objects to be sent... %1%"
2857                  ).arg(int(50 + 20 * (pendingUpload() / maxPendingUpload))))
2858                  time.sleep(0.5)
2859                  QtCore.QCoreApplication.processEvents(
2860                      QtCore.QEventLoop.AllEvents, 1000
2861                  )
2862  
2863              QtCore.QCoreApplication.processEvents(
2864                  QtCore.QEventLoop.AllEvents, 1000
2865              )
2866          QtCore.QCoreApplication.processEvents(
2867              QtCore.QEventLoop.AllEvents, 1000
2868          )
2869  
2870          # save state and geometry self and all widgets
2871          self.updateStatusBar(_translate(
2872              "MainWindow", "Saving settings... %1%").arg(70))
2873          QtCore.QCoreApplication.processEvents(
2874              QtCore.QEventLoop.AllEvents, 1000
2875          )
2876          self.saveSettings()
2877          for attr, obj in self.ui.__dict__.items():
2878              if hasattr(obj, "__class__") \
2879                      and isinstance(obj, settingsmixin.SettingsMixin):
2880                  saveMethod = getattr(obj, "saveSettings", None)
2881                  if callable(saveMethod):
2882                      obj.saveSettings()
2883  
2884          self.updateStatusBar(_translate(
2885              "MainWindow", "Shutting down core... %1%").arg(80))
2886          QtCore.QCoreApplication.processEvents(
2887              QtCore.QEventLoop.AllEvents, 1000
2888          )
2889          shutdown.doCleanShutdown()
2890  
2891          self.updateStatusBar(_translate(
2892              "MainWindow", "Stopping notifications... %1%").arg(90))
2893          self.tray.hide()
2894  
2895          self.updateStatusBar(_translate(
2896              "MainWindow", "Shutdown imminent... %1%").arg(100))
2897  
2898          logger.info("Shutdown complete")
2899          self.close()
2900          # FIXME: rewrite loops with timer instead
2901          if self.wait:
2902              self.destroy()
2903          app.quit()
2904  
2905      def closeEvent(self, event):
2906          """window close event"""
2907          event.ignore()
2908          trayonclose = config.safeGetBoolean(
2909              'bitmessagesettings', 'trayonclose')
2910          if trayonclose:
2911              self.appIndicatorHide()
2912          else:
2913              # custom quit method
2914              self.quit()
2915  
2916      def on_action_InboxMessageForceHtml(self):
2917          msgid = self.getCurrentMessageId()
2918          textEdit = self.getCurrentMessageTextedit()
2919          if not msgid:
2920              return
2921          queryreturn = sqlQuery(
2922              '''select message from inbox where msgid=?''', msgid)
2923          if queryreturn != []:
2924              for row in queryreturn:
2925                  messageText, = row
2926  
2927          lines = messageText.split('\n')
2928          totalLines = len(lines)
2929          for i in range(totalLines):
2930              if 'Message ostensibly from ' in lines[i]:
2931                  lines[i] = '<p style="font-size: 12px; color: grey;">%s</span></p>' % (
2932                      lines[i])
2933              elif lines[i] == '------------------------------------------------------':
2934                  lines[i] = '<hr>'
2935              elif lines[i] == '' and (i+1) < totalLines and \
2936                   lines[i+1] != '------------------------------------------------------':
2937                  lines[i] = '<br><br>'
2938          content = ' '.join(lines) # To keep the whitespace between lines
2939          content = shared.fixPotentiallyInvalidUTF8Data(content)
2940          content = str(content, 'utf-8)')
2941          textEdit.setHtml(QtCore.QString(content))
2942  
2943      def on_action_InboxMarkUnread(self):
2944          tableWidget = self.getCurrentMessagelist()
2945          if not tableWidget:
2946              return
2947  
2948          msgids = set()
2949          # modified = 0
2950          for row in tableWidget.selectedIndexes():
2951              currentRow = row.row()
2952              msgid = tableWidget.item(currentRow, 3).data()
2953              msgids.add(msgid)
2954              # if not tableWidget.item(currentRow, 0).unread:
2955              #     modified += 1
2956              self.updateUnreadStatus(tableWidget, currentRow, msgid, False)
2957  
2958          # for 1081
2959          idCount = len(msgids)
2960          # rowcount =
2961          sqlExecuteChunked(
2962              '''UPDATE inbox SET read=0 WHERE msgid IN ({0}) AND read=1''',
2963              idCount, *msgids
2964          )
2965  
2966          self.propagateUnreadCount()
2967          # tableWidget.selectRow(currentRow + 1)
2968          # This doesn't de-select the last message if you try to mark it
2969          # unread, but that doesn't interfere. Might not be necessary.
2970          # We could also select upwards, but then our problem would be
2971          # with the topmost message.
2972          # tableWidget.clearSelection() manages to mark the message
2973          # as read again.
2974  
2975      # Format predefined text on message reply.
2976      def quoted_text(self, message):
2977          if not config.safeGetBoolean('bitmessagesettings', 'replybelow'):
2978              return '\n\n------------------------------------------------------\n' + message
2979  
2980          quoteWrapper = textwrap.TextWrapper(
2981              replace_whitespace=False, initial_indent='> ',
2982              subsequent_indent='> ', break_long_words=False,
2983              break_on_hyphens=False)
2984  
2985          def quote_line(line):
2986              # Do quote empty lines.
2987              if line == '' or line.isspace():
2988                  return '> '
2989              # Quote already quoted lines, but do not wrap them.
2990              elif line[0:2] == '> ':
2991                  return '> ' + line
2992              # Wrap and quote lines/paragraphs new to this message.
2993              else:
2994                  return quoteWrapper.fill(line)
2995          return '\n'.join([quote_line(l) for l in message.splitlines()]) + '\n\n'
2996  
2997      def setSendFromComboBox(self, address=None):
2998          if address is None:
2999              messagelist = self.getCurrentMessagelist()
3000              if not messagelist:
3001                  return
3002              currentInboxRow = messagelist.currentRow()
3003              address = messagelist.item(currentInboxRow, 0).address
3004          for box in (
3005              self.ui.comboBoxSendFrom, self.ui.comboBoxSendFromBroadcast
3006          ):
3007              for i in range(box.count()):
3008                  if str(box.itemData(i).toPyObject()) == address:
3009                      box.setCurrentIndex(i)
3010                      break
3011              else:
3012                  box.setCurrentIndex(0)
3013  
3014      def on_action_InboxReplyChan(self):
3015          self.on_action_InboxReply(self.REPLY_TYPE_CHAN)
3016  
3017      def on_action_SentReply(self):
3018          self.on_action_InboxReply(self.REPLY_TYPE_UPD)
3019  
3020      def on_action_InboxReply(self, reply_type=None):
3021          """Handle any reply action depending on reply_type"""
3022          # pylint: disable=too-many-locals
3023          tableWidget = self.getCurrentMessagelist()
3024          if not tableWidget:
3025              return
3026  
3027          if reply_type is None:
3028              reply_type = self.REPLY_TYPE_SENDER
3029  
3030          # save this to return back after reply is done
3031          self.replyFromTab = self.ui.tabWidget.currentIndex()
3032  
3033          column_to = 1 if reply_type == self.REPLY_TYPE_UPD else 0
3034          column_from = 0 if reply_type == self.REPLY_TYPE_UPD else 1
3035  
3036          currentInboxRow = tableWidget.currentRow()
3037          toAddressAtCurrentInboxRow = tableWidget.item(
3038              currentInboxRow, column_to).address
3039          acct = accountClass(toAddressAtCurrentInboxRow)
3040          fromAddressAtCurrentInboxRow = tableWidget.item(
3041              currentInboxRow, column_from).address
3042          msgid = tableWidget.item(currentInboxRow, 3).data()
3043          queryreturn = sqlQuery(
3044              "SELECT message FROM inbox WHERE msgid=?", msgid
3045          ) or sqlQuery("SELECT message FROM sent WHERE ackdata=?", msgid)
3046          if queryreturn != []:
3047              for row in queryreturn:
3048                  messageAtCurrentInboxRow, = row
3049          acct.parseMessage(
3050              toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow,
3051              tableWidget.item(currentInboxRow, 2).subject,
3052              messageAtCurrentInboxRow)
3053          widget = {
3054              'subject': self.ui.lineEditSubject,
3055              'from': self.ui.comboBoxSendFrom,
3056              'message': self.ui.textEditMessage
3057          }
3058  
3059          if toAddressAtCurrentInboxRow == str_broadcast_subscribers:
3060              self.ui.tabWidgetSend.setCurrentIndex(
3061                  self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
3062              )
3063  #            toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
3064          elif not config.has_section(toAddressAtCurrentInboxRow):
3065              QtGui.QMessageBox.information(
3066                  self, _translate("MainWindow", "Address is gone"),
3067                  _translate(
3068                      "MainWindow",
3069                      "Bitmessage cannot find your address %1. Perhaps you"
3070                      " removed it?"
3071                  ).arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok)
3072          elif not config.getboolean(
3073                  toAddressAtCurrentInboxRow, 'enabled'):
3074              QtGui.QMessageBox.information(
3075                  self, _translate("MainWindow", "Address disabled"),
3076                  _translate(
3077                      "MainWindow",
3078                      "Error: The address from which you are trying to send"
3079                      " is disabled. You\'ll have to enable it on the"
3080                      " \'Your Identities\' tab before using it."
3081                  ), QtGui.QMessageBox.Ok)
3082          else:
3083              self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow)
3084              broadcast_tab_index = self.ui.tabWidgetSend.indexOf(
3085                  self.ui.sendBroadcast
3086              )
3087              if self.ui.tabWidgetSend.currentIndex() == broadcast_tab_index:
3088                  widget = {
3089                      'subject': self.ui.lineEditSubjectBroadcast,
3090                      'from': self.ui.comboBoxSendFromBroadcast,
3091                      'message': self.ui.textEditMessageBroadcast
3092                  }
3093                  self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index)
3094                  toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
3095          if fromAddressAtCurrentInboxRow == \
3096              tableWidget.item(currentInboxRow, column_from).label or (
3097                  isinstance(acct, GatewayAccount) and
3098                  fromAddressAtCurrentInboxRow == acct.relayAddress):
3099              self.ui.lineEditTo.setText(str(acct.fromAddress))
3100          else:
3101              self.ui.lineEditTo.setText(
3102                  tableWidget.item(currentInboxRow, column_from).accountString()
3103              )
3104  
3105          # If the previous message was to a chan then we should send our
3106          # reply to the chan rather than to the particular person who sent
3107          # the message.
3108          if acct.type == AccountMixin.CHAN and reply_type == self.REPLY_TYPE_CHAN:
3109              logger.debug(
3110                  'Original sent to a chan. Setting the to address in the'
3111                  ' reply to the chan address.')
3112              if toAddressAtCurrentInboxRow == \
3113                      tableWidget.item(currentInboxRow, column_to).label:
3114                  self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow))
3115              else:
3116                  self.ui.lineEditTo.setText(
3117                      tableWidget.item(currentInboxRow, column_to).accountString()
3118                  )
3119  
3120          self.setSendFromComboBox(toAddressAtCurrentInboxRow)
3121  
3122          quotedText = self.quoted_text(
3123              str(messageAtCurrentInboxRow, 'utf-8', 'replace'))
3124          widget['message'].setPlainText(quotedText)
3125          if acct.subject[0:3] in ('Re:', 'RE:'):
3126              widget['subject'].setText(
3127                  tableWidget.item(currentInboxRow, 2).label)
3128          else:
3129              widget['subject'].setText(
3130                  'Re: ' + tableWidget.item(currentInboxRow, 2).label)
3131          self.ui.tabWidget.setCurrentIndex(
3132              self.ui.tabWidget.indexOf(self.ui.send)
3133          )
3134          widget['message'].setFocus()
3135  
3136      def on_action_InboxAddSenderToAddressBook(self):
3137          tableWidget = self.getCurrentMessagelist()
3138          if not tableWidget:
3139              return
3140          currentInboxRow = tableWidget.currentRow()
3141          addressAtCurrentInboxRow = tableWidget.item(
3142              currentInboxRow, 1).data(QtCore.Qt.UserRole)
3143          self.ui.tabWidget.setCurrentIndex(
3144              self.ui.tabWidget.indexOf(self.ui.send)
3145          )
3146          self.click_pushButtonAddAddressBook(
3147              dialogs.AddAddressDialog(self, addressAtCurrentInboxRow))
3148  
3149      def on_action_InboxAddSenderToBlackList(self):
3150          tableWidget = self.getCurrentMessagelist()
3151          if not tableWidget:
3152              return
3153          currentInboxRow = tableWidget.currentRow()
3154          addressAtCurrentInboxRow = tableWidget.item(
3155              currentInboxRow, 1).data(QtCore.Qt.UserRole)
3156          recipientAddress = tableWidget.item(
3157              currentInboxRow, 0).data(QtCore.Qt.UserRole)
3158          # Let's make sure that it isn't already in the address book
3159          queryreturn = sqlQuery('''select * from blacklist where address=?''',
3160                                 addressAtCurrentInboxRow)
3161          if queryreturn == []:
3162              label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + config.get(
3163                  recipientAddress, "label")
3164              sqlExecute('''INSERT INTO blacklist VALUES (?,?, ?)''',
3165                         label,
3166                         addressAtCurrentInboxRow, True)
3167              self.ui.blackwhitelist.rerenderBlackWhiteList()
3168              self.updateStatusBar(_translate(
3169                  "MainWindow",
3170                  "Entry added to the blacklist. Edit the label to your liking.")
3171              )
3172          else:
3173              self.updateStatusBar(_translate(
3174                  "MainWindow",
3175                  "Error: You cannot add the same address to your blacklist"
3176                  " twice. Try renaming the existing one if you want."))
3177  
3178      def deleteRowFromMessagelist(
3179          self, row=None, inventoryHash=None, ackData=None, messageLists=None
3180      ):
3181          if messageLists is None:
3182              messageLists = (
3183                  self.ui.tableWidgetInbox,
3184                  self.ui.tableWidgetInboxChans,
3185                  self.ui.tableWidgetInboxSubscriptions
3186              )
3187          elif type(messageLists) not in (list, tuple):
3188              messageLists = (messageLists,)
3189          for messageList in messageLists:
3190              if row is not None:
3191                  inventoryHash = messageList.item(row, 3).data()
3192                  messageList.removeRow(row)
3193              elif inventoryHash is not None:
3194                  for i in range(messageList.rowCount() - 1, -1, -1):
3195                      if messageList.item(i, 3).data() == inventoryHash:
3196                          messageList.removeRow(i)
3197              elif ackData is not None:
3198                  for i in range(messageList.rowCount() - 1, -1, -1):
3199                      if messageList.item(i, 3).data() == ackData:
3200                          messageList.removeRow(i)
3201  
3202      # Send item on the Inbox tab to trash
3203      def on_action_InboxTrash(self):
3204          tableWidget = self.getCurrentMessagelist()
3205          if not tableWidget:
3206              return
3207          currentRow = 0
3208          folder = self.getCurrentFolder()
3209          shifted = QtGui.QApplication.queryKeyboardModifiers() \
3210              & QtCore.Qt.ShiftModifier
3211          tableWidget.setUpdatesEnabled(False)
3212          inventoryHashesToTrash = set()
3213          # ranges in reversed order
3214          for r in sorted(
3215              tableWidget.selectedRanges(), key=lambda r: r.topRow()
3216          )[::-1]:
3217              for i in range(r.bottomRow() - r.topRow() + 1):
3218                  inventoryHashesToTrash.add(
3219                      tableWidget.item(r.topRow() + i, 3).data())
3220              currentRow = r.topRow()
3221              self.getCurrentMessageTextedit().setText("")
3222              tableWidget.model().removeRows(
3223                  r.topRow(), r.bottomRow() - r.topRow() + 1)
3224          idCount = len(inventoryHashesToTrash)
3225          sqlExecuteChunked(
3226              ("DELETE FROM inbox" if folder == "trash" or shifted else
3227               "UPDATE inbox SET folder='trash', read=1") +
3228              " WHERE msgid IN ({0})", idCount, *inventoryHashesToTrash)
3229          tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1)
3230          tableWidget.setUpdatesEnabled(True)
3231          self.propagateUnreadCount(folder)
3232          self.updateStatusBar(_translate("MainWindow", "Moved items to trash."))
3233  
3234      def on_action_TrashUndelete(self):
3235          tableWidget = self.getCurrentMessagelist()
3236          if not tableWidget:
3237              return
3238          currentRow = 0
3239          tableWidget.setUpdatesEnabled(False)
3240          inventoryHashesToTrash = set()
3241          # ranges in reversed order
3242          for r in sorted(
3243              tableWidget.selectedRanges(), key=lambda r: r.topRow()
3244          )[::-1]:
3245              for i in range(r.bottomRow() - r.topRow() + 1):
3246                  inventoryHashesToTrash.add(
3247                      tableWidget.item(r.topRow() + i, 3).data())
3248              currentRow = r.topRow()
3249              self.getCurrentMessageTextedit().setText("")
3250              tableWidget.model().removeRows(
3251                  r.topRow(), r.bottomRow() - r.topRow() + 1)
3252          tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1)
3253          idCount = len(inventoryHashesToTrash)
3254          sqlExecuteChunked(
3255              "UPDATE inbox SET folder='inbox' WHERE msgid IN({0})",
3256              idCount, *inventoryHashesToTrash)
3257          tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1)
3258          tableWidget.setUpdatesEnabled(True)
3259          self.propagateUnreadCount()
3260          self.updateStatusBar(_translate("MainWindow", "Undeleted item."))
3261  
3262      def on_action_InboxSaveMessageAs(self):
3263          tableWidget = self.getCurrentMessagelist()
3264          if not tableWidget:
3265              return
3266          currentInboxRow = tableWidget.currentRow()
3267          try:
3268              subjectAtCurrentInboxRow = str(tableWidget.item(
3269                  currentInboxRow, 2).data(QtCore.Qt.UserRole))
3270          except:
3271              subjectAtCurrentInboxRow = ''
3272  
3273          # Retrieve the message data out of the SQL database
3274          msgid = tableWidget.item(currentInboxRow, 3).data()
3275          queryreturn = sqlQuery(
3276              '''select message from inbox where msgid=?''', msgid)
3277          if queryreturn != []:
3278              for row in queryreturn:
3279                  message, = row
3280  
3281          defaultFilename = "".join(x for x in subjectAtCurrentInboxRow if x.isalnum()) + '.txt'
3282          filename = QtGui.QFileDialog.getSaveFileName(
3283              self,
3284              _translate("MainWindow","Save As..."),
3285              defaultFilename,
3286              "Text files (*.txt);;All files (*.*)")
3287          if filename == '':
3288              return
3289          try:
3290              f = open(filename, 'w')
3291              f.write(message)
3292              f.close()
3293          except Exception:
3294              logger.exception('Message not saved', exc_info=True)
3295              self.updateStatusBar(_translate("MainWindow", "Write error."))
3296  
3297      # Send item on the Sent tab to trash
3298      def on_action_SentTrash(self):
3299          tableWidget = self.getCurrentMessagelist()
3300          if not tableWidget:
3301              return
3302          folder = self.getCurrentFolder()
3303          shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier
3304          while tableWidget.selectedIndexes() != []:
3305              currentRow = tableWidget.selectedIndexes()[0].row()
3306              ackdataToTrash = tableWidget.item(currentRow, 3).data()
3307              sqlExecute(
3308                  "DELETE FROM sent" if folder == "trash" or shifted else
3309                  "UPDATE sent SET folder='trash'"
3310                  " WHERE ackdata = ?", ackdataToTrash
3311              )
3312              self.getCurrentMessageTextedit().setPlainText("")
3313              tableWidget.removeRow(currentRow)
3314              self.updateStatusBar(_translate(
3315                  "MainWindow", "Moved items to trash."))
3316  
3317          self.ui.tableWidgetInbox.selectRow(
3318              currentRow if currentRow == 0 else currentRow - 1)
3319  
3320      def on_action_ForceSend(self):
3321          currentRow = self.ui.tableWidgetInbox.currentRow()
3322          addressAtCurrentRow = self.ui.tableWidgetInbox.item(
3323              currentRow, 0).data(QtCore.Qt.UserRole)
3324          toRipe = decodeAddress(addressAtCurrentRow)[3]
3325          sqlExecute(
3326              '''UPDATE sent SET status='forcepow' WHERE toripe=? AND status='toodifficult' and folder='sent' ''',
3327              toRipe)
3328          queryreturn = sqlQuery('''select ackdata FROM sent WHERE status='forcepow' ''')
3329          for row in queryreturn:
3330              ackdata, = row
3331              queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (
3332                  ackdata, 'Overriding maximum-difficulty setting. Work queued.')))
3333          queues.workerQueue.put(('sendmessage', ''))
3334  
3335      def on_action_SentClipboard(self):
3336          currentRow = self.ui.tableWidgetInbox.currentRow()
3337          addressAtCurrentRow = self.ui.tableWidgetInbox.item(
3338              currentRow, 0).data(QtCore.Qt.UserRole)
3339          clipboard = QtGui.QApplication.clipboard()
3340          clipboard.setText(str(addressAtCurrentRow))
3341  
3342      # Group of functions for the Address Book dialog box
3343      def on_action_AddressBookNew(self):
3344          self.click_pushButtonAddAddressBook()
3345  
3346      def on_action_AddressBookDelete(self):
3347          while self.ui.tableWidgetAddressBook.selectedIndexes() != []:
3348              currentRow = self.ui.tableWidgetAddressBook.selectedIndexes()[
3349                  0].row()
3350              item = self.ui.tableWidgetAddressBook.item(currentRow, 0)
3351              sqlExecute(
3352                  'DELETE FROM addressbook WHERE address=?', item.address)
3353              self.ui.tableWidgetAddressBook.removeRow(currentRow)
3354          self.rerenderMessagelistFromLabels()
3355          self.rerenderMessagelistToLabels()
3356  
3357      def on_action_AddressBookClipboard(self):
3358          addresses_string = ''
3359          for item in self.getAddressbookSelectedItems():
3360              if addresses_string == '':
3361                  addresses_string = item.address
3362              else:
3363                  addresses_string += ', ' + item.address
3364          clipboard = QtGui.QApplication.clipboard()
3365          clipboard.setText(addresses_string)
3366  
3367      def on_action_AddressBookSend(self):
3368          selected_items = self.getAddressbookSelectedItems()
3369  
3370          if not selected_items:  # FIXME: impossible
3371              return self.updateStatusBar(_translate(
3372                  "MainWindow", "No addresses selected."))
3373  
3374          addresses_string = str(
3375              self.ui.lineEditTo.text().toUtf8(), 'utf-8')
3376          for item in selected_items:
3377              address_string = item.accountString()
3378              if not addresses_string:
3379                  addresses_string = address_string
3380              else:
3381                  addresses_string += '; ' + address_string
3382  
3383          self.ui.lineEditTo.setText(addresses_string)
3384          self.statusbar.clearMessage()
3385          self.ui.tabWidget.setCurrentIndex(
3386              self.ui.tabWidget.indexOf(self.ui.send)
3387          )
3388  
3389      def on_action_AddressBookSubscribe(self):
3390          for item in self.getAddressbookSelectedItems():
3391              # Then subscribe to it...
3392              # provided it's not already in the address book
3393              if shared.isAddressInMySubscriptionsList(item.address):
3394                  self.updateStatusBar(_translate(
3395                      "MainWindow",
3396                      "Error: You cannot add the same address to your"
3397                      " subscriptions twice. Perhaps rename the existing"
3398                      " one if you want."))
3399                  continue
3400              self.addSubscription(item.address, item.label)
3401              self.ui.tabWidget.setCurrentIndex(
3402                  self.ui.tabWidget.indexOf(self.ui.subscriptions)
3403              )
3404  
3405      def on_context_menuAddressBook(self, point):
3406          self.popMenuAddressBook = QtGui.QMenu(self)
3407          self.popMenuAddressBook.addAction(self.actionAddressBookSend)
3408          self.popMenuAddressBook.addAction(self.actionAddressBookClipboard)
3409          self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe)
3410          self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar)
3411          self.popMenuAddressBook.addAction(self.actionAddressBookSetSound)
3412          self.popMenuAddressBook.addSeparator()
3413          self.popMenuAddressBook.addAction(self.actionAddressBookNew)
3414          normal = True
3415          selected_items = self.getAddressbookSelectedItems()
3416          for item in selected_items:
3417              if item.type != AccountMixin.NORMAL:
3418                  normal = False
3419                  break
3420          if normal:
3421              # only if all selected addressbook items are normal, allow delete
3422              self.popMenuAddressBook.addAction(self.actionAddressBookDelete)
3423          if len(selected_items) == 1:
3424              self._contact_selected = selected_items.pop()
3425              self.popMenuAddressBook.addSeparator()
3426              for plugin in self.menu_plugins['address']:
3427                  self.popMenuAddressBook.addAction(plugin)
3428          self.popMenuAddressBook.exec_(
3429              self.ui.tableWidgetAddressBook.mapToGlobal(point))
3430  
3431      # Group of functions for the Subscriptions dialog box
3432      def on_action_SubscriptionsNew(self):
3433          self.click_pushButtonAddSubscription()
3434  
3435      def on_action_SubscriptionsDelete(self):
3436          if QtGui.QMessageBox.question(
3437                  self, "Delete subscription?",
3438                  _translate(
3439                      "MainWindow",
3440                      "If you delete the subscription, messages that you"
3441                      " already received will become inaccessible. Maybe"
3442                      " you can consider disabling the subscription instead."
3443                      " Disabled subscriptions will not receive new"
3444                      " messages, but you can still view messages you"
3445                      " already received.\n\nAre you sure you want to"
3446                      " delete the subscription?"
3447                  ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
3448          ) != QtGui.QMessageBox.Yes:
3449              return
3450          address = self.getCurrentAccount()
3451          sqlExecute('''DELETE FROM subscriptions WHERE address=?''',
3452                     address)
3453          self.rerenderTabTreeSubscriptions()
3454          self.rerenderMessagelistFromLabels()
3455          self.rerenderAddressBook()
3456          shared.reloadBroadcastSendersForWhichImWatching()
3457  
3458      def on_action_SubscriptionsClipboard(self):
3459          address = self.getCurrentAccount()
3460          clipboard = QtGui.QApplication.clipboard()
3461          clipboard.setText(str(address))
3462  
3463      def on_action_SubscriptionsEnable(self):
3464          address = self.getCurrentAccount()
3465          sqlExecute(
3466              '''update subscriptions set enabled=1 WHERE address=?''',
3467              address)
3468          account = self.getCurrentItem()
3469          account.setEnabled(True)
3470          self.rerenderAddressBook()
3471          shared.reloadBroadcastSendersForWhichImWatching()
3472  
3473      def on_action_SubscriptionsDisable(self):
3474          address = self.getCurrentAccount()
3475          sqlExecute(
3476              '''update subscriptions set enabled=0 WHERE address=?''',
3477              address)
3478          account = self.getCurrentItem()
3479          account.setEnabled(False)
3480          self.rerenderAddressBook()
3481          shared.reloadBroadcastSendersForWhichImWatching()
3482  
3483      def on_context_menuSubscriptions(self, point):
3484          currentItem = self.getCurrentItem()
3485          self.popMenuSubscriptions = QtGui.QMenu(self)
3486          if isinstance(currentItem, Ui_AddressWidget):
3487              self.popMenuSubscriptions.addAction(self.actionsubscriptionsNew)
3488              self.popMenuSubscriptions.addAction(self.actionsubscriptionsDelete)
3489              self.popMenuSubscriptions.addSeparator()
3490              if currentItem.isEnabled:
3491                  self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable)
3492              else:
3493                  self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable)
3494              self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar)
3495              self.popMenuSubscriptions.addSeparator()
3496              self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard)
3497              self.popMenuSubscriptions.addAction(self.actionsubscriptionsSend)
3498              self.popMenuSubscriptions.addSeparator()
3499  
3500              self._contact_selected = currentItem
3501              # preloaded gui.menu plugins with prefix 'address'
3502              for plugin in self.menu_plugins['address']:
3503                  self.popMenuSubscriptions.addAction(plugin)
3504              self.popMenuSubscriptions.addSeparator()
3505          if self.getCurrentFolder() != 'sent':
3506              self.popMenuSubscriptions.addAction(self.actionMarkAllRead)
3507          if self.popMenuSubscriptions.isEmpty():
3508              return
3509          self.popMenuSubscriptions.exec_(
3510              self.ui.treeWidgetSubscriptions.mapToGlobal(point))
3511  
3512      def widgetConvert(self, widget):
3513          if widget == self.ui.tableWidgetInbox:
3514              return self.ui.treeWidgetYourIdentities
3515          elif widget == self.ui.tableWidgetInboxSubscriptions:
3516              return self.ui.treeWidgetSubscriptions
3517          elif widget == self.ui.tableWidgetInboxChans:
3518              return self.ui.treeWidgetChans
3519          elif widget == self.ui.treeWidgetYourIdentities:
3520              return self.ui.tableWidgetInbox
3521          elif widget == self.ui.treeWidgetSubscriptions:
3522              return self.ui.tableWidgetInboxSubscriptions
3523          elif widget == self.ui.treeWidgetChans:
3524              return self.ui.tableWidgetInboxChans
3525          else:
3526              return None
3527  
3528      def getCurrentTreeWidget(self):
3529          currentIndex = self.ui.tabWidget.currentIndex()
3530          treeWidgetList = (
3531              self.ui.treeWidgetYourIdentities,
3532              False,
3533              self.ui.treeWidgetSubscriptions,
3534              self.ui.treeWidgetChans
3535          )
3536          if currentIndex >= 0 and currentIndex < len(treeWidgetList):
3537              return treeWidgetList[currentIndex]
3538          else:
3539              return False
3540  
3541      def getAccountTreeWidget(self, account):
3542          try:
3543              if account.type == AccountMixin.CHAN:
3544                  return self.ui.treeWidgetChans
3545              elif account.type == AccountMixin.SUBSCRIPTION:
3546                  return self.ui.treeWidgetSubscriptions
3547              else:
3548                  return self.ui.treeWidgetYourIdentities
3549          except:
3550              return self.ui.treeWidgetYourIdentities
3551  
3552      def getCurrentMessagelist(self):
3553          currentIndex = self.ui.tabWidget.currentIndex()
3554          messagelistList = (
3555              self.ui.tableWidgetInbox,
3556              False,
3557              self.ui.tableWidgetInboxSubscriptions,
3558              self.ui.tableWidgetInboxChans,
3559          )
3560          if currentIndex >= 0 and currentIndex < len(messagelistList):
3561              return messagelistList[currentIndex]
3562  
3563      def getAccountMessagelist(self, account):
3564          try:
3565              if account.type == AccountMixin.CHAN:
3566                  return self.ui.tableWidgetInboxChans
3567              elif account.type == AccountMixin.SUBSCRIPTION:
3568                  return self.ui.tableWidgetInboxSubscriptions
3569              else:
3570                  return self.ui.tableWidgetInbox
3571          except:
3572              return self.ui.tableWidgetInbox
3573  
3574      def getCurrentMessageId(self):
3575          messagelist = self.getCurrentMessagelist()
3576          if messagelist:
3577              currentRow = messagelist.currentRow()
3578              if currentRow >= 0:
3579                  return messagelist.item(currentRow, 3).data()
3580  
3581      def getCurrentMessageTextedit(self):
3582          currentIndex = self.ui.tabWidget.currentIndex()
3583          messagelistList = (
3584              self.ui.textEditInboxMessage,
3585              False,
3586              self.ui.textEditInboxMessageSubscriptions,
3587              self.ui.textEditInboxMessageChans,
3588          )
3589          if currentIndex >= 0 and currentIndex < len(messagelistList):
3590              return messagelistList[currentIndex]
3591  
3592      def getAccountTextedit(self, account):
3593          try:
3594              if account.type == AccountMixin.CHAN:
3595                  return self.ui.textEditInboxMessageChans
3596              elif account.type == AccountMixin.SUBSCRIPTION:
3597                  return self.ui.textEditInboxSubscriptions
3598              else:
3599                  return self.ui.textEditInboxMessage
3600          except:
3601              return self.ui.textEditInboxMessage
3602  
3603      def getCurrentSearchLine(self, currentIndex=None, retObj=False):
3604          if currentIndex is None:
3605              currentIndex = self.ui.tabWidget.currentIndex()
3606          messagelistList = (
3607              self.ui.inboxSearchLineEdit,
3608              False,
3609              self.ui.inboxSearchLineEditSubscriptions,
3610              self.ui.inboxSearchLineEditChans,
3611          )
3612          if currentIndex >= 0 and currentIndex < len(messagelistList):
3613              return (
3614                  messagelistList[currentIndex] if retObj
3615                  else messagelistList[currentIndex].text().toUtf8().data())
3616  
3617      def getCurrentSearchOption(self, currentIndex=None):
3618          if currentIndex is None:
3619              currentIndex = self.ui.tabWidget.currentIndex()
3620          messagelistList = (
3621              self.ui.inboxSearchOption,
3622              False,
3623              self.ui.inboxSearchOptionSubscriptions,
3624              self.ui.inboxSearchOptionChans,
3625          )
3626          if currentIndex >= 0 and currentIndex < len(messagelistList):
3627              return messagelistList[currentIndex].currentText()
3628  
3629      # Group of functions for the Your Identities dialog box
3630      def getCurrentItem(self, treeWidget=None):
3631          if treeWidget is None:
3632              treeWidget = self.getCurrentTreeWidget()
3633          if treeWidget:
3634              return treeWidget.currentItem()
3635  
3636      def getCurrentAccount(self, treeWidget=None):
3637          currentItem = self.getCurrentItem(treeWidget)
3638          if currentItem:
3639              return currentItem.address
3640  
3641      def getCurrentFolder(self, treeWidget=None):
3642          currentItem = self.getCurrentItem(treeWidget)
3643          try:
3644              return currentItem.folderName
3645          except AttributeError:
3646              pass
3647  
3648      def setCurrentItemColor(self, color):
3649          currentItem = self.getCurrentItem()
3650          if currentItem:
3651              brush = QtGui.QBrush()
3652              brush.setStyle(QtCore.Qt.NoBrush)
3653              brush.setColor(color)
3654              currentItem.setForeground(0, brush)
3655  
3656      def getAddressbookSelectedItems(self):
3657          return [
3658              self.ui.tableWidgetAddressBook.item(i.row(), 0)
3659              for i in self.ui.tableWidgetAddressBook.selectedIndexes()
3660              if i.column() == 0
3661          ]
3662  
3663      def on_action_YourIdentitiesNew(self):
3664          self.click_NewAddressDialog()
3665  
3666      def on_action_YourIdentitiesDelete(self):
3667          account = self.getCurrentItem()
3668          if account.type == AccountMixin.NORMAL:
3669              return  # maybe in the future
3670          elif account.type == AccountMixin.CHAN:
3671              if QtGui.QMessageBox.question(
3672                      self, "Delete channel?",
3673                      _translate(
3674                          "MainWindow",
3675                          "If you delete the channel, messages that you"
3676                          " already received will become inaccessible."
3677                          " Maybe you can consider disabling the channel"
3678                          " instead. Disabled channels will not receive new"
3679                          " messages, but you can still view messages you"
3680                          " already received.\n\nAre you sure you want to"
3681                          " delete the channel?"
3682                      ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
3683              ) == QtGui.QMessageBox.Yes:
3684                  config.remove_section(str(account.address))
3685              else:
3686                  return
3687          else:
3688              return
3689          config.save()
3690          shared.reloadMyAddressHashes()
3691          self.rerenderAddressBook()
3692          self.rerenderComboBoxSendFrom()
3693          if account.type == AccountMixin.NORMAL:
3694              self.rerenderTabTreeMessages()
3695          elif account.type == AccountMixin.CHAN:
3696              self.rerenderTabTreeChans()
3697  
3698      def on_action_Enable(self):
3699          addressAtCurrentRow = self.getCurrentAccount()
3700          self.enableIdentity(addressAtCurrentRow)
3701          account = self.getCurrentItem()
3702          account.setEnabled(True)
3703  
3704      def enableIdentity(self, address):
3705          config.set(address, 'enabled', 'true')
3706          config.save()
3707          shared.reloadMyAddressHashes()
3708          self.rerenderAddressBook()
3709  
3710      def on_action_Disable(self):
3711          address = self.getCurrentAccount()
3712          self.disableIdentity(address)
3713          account = self.getCurrentItem()
3714          account.setEnabled(False)
3715  
3716      def disableIdentity(self, address):
3717          config.set(str(address), 'enabled', 'false')
3718          config.save()
3719          shared.reloadMyAddressHashes()
3720          self.rerenderAddressBook()
3721  
3722      def on_action_Clipboard(self):
3723          address = self.getCurrentAccount()
3724          clipboard = QtGui.QApplication.clipboard()
3725          clipboard.setText(str(address))
3726  
3727      def on_action_ClipboardMessagelist(self):
3728          tableWidget = self.getCurrentMessagelist()
3729          currentColumn = tableWidget.currentColumn()
3730          currentRow = tableWidget.currentRow()
3731          currentFolder = self.getCurrentFolder()
3732          if currentColumn not in (0, 1, 2):  # to, from, subject
3733              currentColumn = 0 if currentFolder == "sent" else 1
3734  
3735          if currentFolder == "sent":
3736              myAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole)
3737              otherAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole)
3738          else:
3739              myAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole)
3740              otherAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole)
3741          account = accountClass(myAddress)
3742          if isinstance(account, GatewayAccount) and otherAddress == account.relayAddress and (
3743                  (currentColumn in [0, 2] and self.getCurrentFolder() == "sent") or
3744                  (currentColumn in [1, 2] and self.getCurrentFolder() != "sent")):
3745              text = str(tableWidget.item(currentRow, currentColumn).label)
3746          else:
3747              text = tableWidget.item(currentRow, currentColumn).data(QtCore.Qt.UserRole)
3748  
3749          clipboard = QtGui.QApplication.clipboard()
3750          clipboard.setText(text)
3751  
3752      # set avatar functions
3753      def on_action_TreeWidgetSetAvatar(self):
3754          address = self.getCurrentAccount()
3755          self.setAvatar(address)
3756  
3757      def on_action_AddressBookSetAvatar(self):
3758          self.on_action_SetAvatar(self.ui.tableWidgetAddressBook)
3759  
3760      def on_action_SetAvatar(self, thisTableWidget):
3761          currentRow = thisTableWidget.currentRow()
3762          addressAtCurrentRow = thisTableWidget.item(
3763              currentRow, 1).text()
3764          setToIdenticon = not self.setAvatar(addressAtCurrentRow)
3765          if setToIdenticon:
3766              thisTableWidget.item(
3767                  currentRow, 0).setIcon(avatarize(addressAtCurrentRow))
3768  
3769      # TODO: reuse utils
3770      def setAvatar(self, addressAtCurrentRow):
3771          if not os.path.exists(state.appdata + 'avatars/'):
3772              os.makedirs(state.appdata + 'avatars/')
3773          hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest()
3774          extensions = [
3775              'PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM',
3776              'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA']
3777  
3778          names = {
3779              'BMP': 'Windows Bitmap',
3780              'GIF': 'Graphic Interchange Format',
3781              'JPG': 'Joint Photographic Experts Group',
3782              'JPEG': 'Joint Photographic Experts Group',
3783              'MNG': 'Multiple-image Network Graphics',
3784              'PNG': 'Portable Network Graphics',
3785              'PBM': 'Portable Bitmap',
3786              'PGM': 'Portable Graymap',
3787              'PPM': 'Portable Pixmap',
3788              'TIFF': 'Tagged Image File Format',
3789              'XBM': 'X11 Bitmap',
3790              'XPM': 'X11 Pixmap',
3791              'SVG': 'Scalable Vector Graphics',
3792              'TGA': 'Targa Image Format'}
3793          filters = []
3794          all_images_filter = []
3795          current_files = []
3796          for ext in extensions:
3797              filters += [names[ext] + ' (*.' + ext.lower() + ')']
3798              all_images_filter += ['*.' + ext.lower()]
3799              upper = state.appdata + 'avatars/' + hash + '.' + ext.upper()
3800              lower = state.appdata + 'avatars/' + hash + '.' + ext.lower()
3801              if os.path.isfile(lower):
3802                  current_files += [lower]
3803              elif os.path.isfile(upper):
3804                  current_files += [upper]
3805          filters[0:0] = ['Image files (' + ' '.join(all_images_filter) + ')']
3806          filters[1:1] = ['All files (*.*)']
3807          sourcefile = QtGui.QFileDialog.getOpenFileName(
3808              self, _translate("MainWindow", "Set avatar..."),
3809              filter=';;'.join(filters)
3810          )
3811          # determine the correct filename (note that avatars don't use the suffix)
3812          destination = state.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1]
3813          exists = QtCore.QFile.exists(destination)
3814          if sourcefile == '':
3815              # ask for removal of avatar
3816              if exists | (len(current_files) > 0):
3817                  displayMsg = _translate(
3818                      "MainWindow", "Do you really want to remove this avatar?")
3819                  overwrite = QtGui.QMessageBox.question(
3820                      self, 'Message', displayMsg,
3821                      QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
3822              else:
3823                  overwrite = QtGui.QMessageBox.No
3824          else:
3825              # ask whether to overwrite old avatar
3826              if exists | (len(current_files) > 0):
3827                  displayMsg = _translate(
3828                      "MainWindow",
3829                      "You have already set an avatar for this address."
3830                      " Do you really want to overwrite it?")
3831                  overwrite = QtGui.QMessageBox.question(
3832                      self, 'Message', displayMsg,
3833                      QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
3834              else:
3835                  overwrite = QtGui.QMessageBox.No
3836  
3837          # copy the image file to the appdata folder
3838          if (not exists) | (overwrite == QtGui.QMessageBox.Yes):
3839              if overwrite == QtGui.QMessageBox.Yes:
3840                  for file in current_files:
3841                      QtCore.QFile.remove(file)
3842                  QtCore.QFile.remove(destination)
3843              # copy it
3844              if sourcefile != '':
3845                  copied = QtCore.QFile.copy(sourcefile, destination)
3846                  if not copied:
3847                      logger.error('couldn\'t copy :(')
3848              # set the icon
3849              self.rerenderTabTreeMessages()
3850              self.rerenderTabTreeSubscriptions()
3851              self.rerenderTabTreeChans()
3852              self.rerenderComboBoxSendFrom()
3853              self.rerenderComboBoxSendFromBroadcast()
3854              self.rerenderMessagelistFromLabels()
3855              self.rerenderMessagelistToLabels()
3856              self.ui.blackwhitelist.rerenderBlackWhiteList()
3857              # generate identicon
3858              return False
3859  
3860          return True
3861  
3862      def on_action_AddressBookSetSound(self):
3863          widget = self.ui.tableWidgetAddressBook
3864          self.setAddressSound(widget.item(widget.currentRow(), 0).text())
3865  
3866      def setAddressSound(self, addr):
3867          filters = [str(_translate(
3868              "MainWindow", "Sound files (%s)" %
3869              ' '.join(['*%s%s' % (os.extsep, ext) for ext in sound.extensions])
3870          ))]
3871          sourcefile = str(QtGui.QFileDialog.getOpenFileName(
3872              self, _translate("MainWindow", "Set notification sound..."),
3873              filter=';;'.join(filters)
3874          ))
3875  
3876          if not sourcefile:
3877              return
3878  
3879          destdir = os.path.join(state.appdata, 'sounds')
3880          destfile = str(addr) + os.path.splitext(sourcefile)[-1]
3881          destination = os.path.join(destdir, destfile)
3882  
3883          if sourcefile == destination:
3884              return
3885  
3886          pattern = destfile.lower()
3887          for item in os.listdir(destdir):
3888              if item.lower() == pattern:
3889                  overwrite = QtGui.QMessageBox.question(
3890                      self, _translate("MainWindow", "Message"),
3891                      _translate(
3892                          "MainWindow",
3893                          "You have already set a notification sound"
3894                          " for this address book entry."
3895                          " Do you really want to overwrite it?"),
3896                      QtGui.QMessageBox.Yes, QtGui.QMessageBox.No
3897                  ) == QtGui.QMessageBox.Yes
3898                  if overwrite:
3899                      QtCore.QFile.remove(os.path.join(destdir, item))
3900                  break
3901  
3902          if not QtCore.QFile.copy(sourcefile, destination):
3903              logger.error(
3904                  'couldn\'t copy %s to %s', sourcefile, destination)
3905  
3906      def on_context_menuYourIdentities(self, point):
3907          currentItem = self.getCurrentItem()
3908          self.popMenuYourIdentities = QtGui.QMenu(self)
3909          if isinstance(currentItem, Ui_AddressWidget):
3910              self.popMenuYourIdentities.addAction(self.actionNewYourIdentities)
3911              self.popMenuYourIdentities.addSeparator()
3912              self.popMenuYourIdentities.addAction(self.actionClipboardYourIdentities)
3913              self.popMenuYourIdentities.addSeparator()
3914              if currentItem.isEnabled:
3915                  self.popMenuYourIdentities.addAction(self.actionDisableYourIdentities)
3916              else:
3917                  self.popMenuYourIdentities.addAction(self.actionEnableYourIdentities)
3918              self.popMenuYourIdentities.addAction(self.actionSetAvatarYourIdentities)
3919              self.popMenuYourIdentities.addAction(self.actionSpecialAddressBehaviorYourIdentities)
3920              self.popMenuYourIdentities.addAction(self.actionEmailGateway)
3921              self.popMenuYourIdentities.addSeparator()
3922              if currentItem.type != AccountMixin.ALL:
3923                  self._contact_selected = currentItem
3924                  # preloaded gui.menu plugins with prefix 'address'
3925                  for plugin in self.menu_plugins['address']:
3926                      self.popMenuYourIdentities.addAction(plugin)
3927              self.popMenuYourIdentities.addSeparator()
3928          if self.getCurrentFolder() != 'sent':
3929              self.popMenuYourIdentities.addAction(self.actionMarkAllRead)
3930          if self.popMenuYourIdentities.isEmpty():
3931              return
3932          self.popMenuYourIdentities.exec_(
3933              self.ui.treeWidgetYourIdentities.mapToGlobal(point))
3934  
3935      # TODO make one popMenu
3936      def on_context_menuChan(self, point):
3937          currentItem = self.getCurrentItem()
3938          self.popMenu = QtGui.QMenu(self)
3939          if isinstance(currentItem, Ui_AddressWidget):
3940              self.popMenu.addAction(self.actionNew)
3941              self.popMenu.addAction(self.actionDelete)
3942              self.popMenu.addSeparator()
3943              if currentItem.isEnabled:
3944                  self.popMenu.addAction(self.actionDisable)
3945              else:
3946                  self.popMenu.addAction(self.actionEnable)
3947              self.popMenu.addAction(self.actionSetAvatar)
3948              self.popMenu.addSeparator()
3949              self.popMenu.addAction(self.actionClipboard)
3950              self.popMenu.addAction(self.actionSend)
3951              self.popMenu.addSeparator()
3952              self._contact_selected = currentItem
3953              # preloaded gui.menu plugins with prefix 'address'
3954              for plugin in self.menu_plugins['address']:
3955                  self.popMenu.addAction(plugin)
3956              self.popMenu.addSeparator()
3957          if self.getCurrentFolder() != 'sent':
3958              self.popMenu.addAction(self.actionMarkAllRead)
3959          if self.popMenu.isEmpty():
3960              return
3961          self.popMenu.exec_(
3962              self.ui.treeWidgetChans.mapToGlobal(point))
3963  
3964      def on_context_menuInbox(self, point):
3965          tableWidget = self.getCurrentMessagelist()
3966          if not tableWidget:
3967              return
3968  
3969          currentFolder = self.getCurrentFolder()
3970          if currentFolder == 'sent':
3971              self.on_context_menuSent(point)
3972              return
3973  
3974          self.popMenuInbox = QtGui.QMenu(self)
3975          self.popMenuInbox.addAction(self.actionForceHtml)
3976          self.popMenuInbox.addAction(self.actionMarkUnread)
3977          self.popMenuInbox.addSeparator()
3978          currentRow = tableWidget.currentRow()
3979          account = accountClass(
3980              tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole))
3981  
3982          if account.type == AccountMixin.CHAN:
3983              self.popMenuInbox.addAction(self.actionReplyChan)
3984          self.popMenuInbox.addAction(self.actionReply)
3985          self.popMenuInbox.addAction(self.actionAddSenderToAddressBook)
3986          self.actionClipboardMessagelist = self.ui.inboxContextMenuToolbar.addAction(
3987              _translate("MainWindow", "Copy subject to clipboard")
3988              if tableWidget.currentColumn() == 2 else
3989              _translate("MainWindow", "Copy address to clipboard"),
3990              self.on_action_ClipboardMessagelist)
3991          self.popMenuInbox.addAction(self.actionClipboardMessagelist)
3992          # pylint: disable=no-member
3993          self._contact_selected = tableWidget.item(currentRow, 1)
3994          # preloaded gui.menu plugins with prefix 'address'
3995          for plugin in self.menu_plugins['address']:
3996              self.popMenuInbox.addAction(plugin)
3997          self.popMenuInbox.addSeparator()
3998          self.popMenuInbox.addAction(self.actionAddSenderToBlackList)
3999          self.popMenuInbox.addSeparator()
4000          self.popMenuInbox.addAction(self.actionSaveMessageAs)
4001          if currentFolder == "trash":
4002              self.popMenuInbox.addAction(self.actionUndeleteTrashedMessage)
4003          else:
4004              self.popMenuInbox.addAction(self.actionTrashInboxMessage)
4005          self.popMenuInbox.exec_(tableWidget.mapToGlobal(point))
4006  
4007      def on_context_menuSent(self, point):
4008          currentRow = self.ui.tableWidgetInbox.currentRow()
4009          self.popMenuSent = QtGui.QMenu(self)
4010          self.popMenuSent.addAction(self.actionSentClipboard)
4011          self._contact_selected = self.ui.tableWidgetInbox.item(currentRow, 0)
4012          # preloaded gui.menu plugins with prefix 'address'
4013          for plugin in self.menu_plugins['address']:
4014              self.popMenuSent.addAction(plugin)
4015          self.popMenuSent.addSeparator()
4016          self.popMenuSent.addAction(self.actionTrashSentMessage)
4017          self.popMenuSent.addAction(self.actionSentReply)
4018  
4019          # Check to see if this item is toodifficult and display an additional
4020          # menu option (Force Send) if it is.
4021          if currentRow >= 0:
4022              ackData = self.ui.tableWidgetInbox.item(currentRow, 3).data()
4023              queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData)
4024              for row in queryreturn:
4025                  status, = row
4026              if status == 'toodifficult':
4027                  self.popMenuSent.addAction(self.actionForceSend)
4028  
4029          self.popMenuSent.exec_(self.ui.tableWidgetInbox.mapToGlobal(point))
4030  
4031      def inboxSearchLineEditUpdated(self, text):
4032          # dynamic search for too short text is slow
4033          text = text.toUtf8()
4034          if 0 < len(text) < 3:
4035              return
4036          messagelist = self.getCurrentMessagelist()
4037          if messagelist:
4038              searchOption = self.getCurrentSearchOption()
4039              account = self.getCurrentAccount()
4040              folder = self.getCurrentFolder()
4041              self.loadMessagelist(
4042                  messagelist, account, folder, searchOption, text)
4043  
4044      def inboxSearchLineEditReturnPressed(self):
4045          logger.debug("Search return pressed")
4046          searchLine = self.getCurrentSearchLine()
4047          messagelist = self.getCurrentMessagelist()
4048          if messagelist and len(str(searchLine)) < 3:
4049              searchOption = self.getCurrentSearchOption()
4050              account = self.getCurrentAccount()
4051              folder = self.getCurrentFolder()
4052              self.loadMessagelist(
4053                  messagelist, account, folder, searchOption, searchLine)
4054              messagelist.setFocus()
4055  
4056      def treeWidgetItemClicked(self):
4057          messagelist = self.getCurrentMessagelist()
4058          if not messagelist:
4059              return
4060          messageTextedit = self.getCurrentMessageTextedit()
4061          if messageTextedit:
4062              messageTextedit.setPlainText("")
4063          account = self.getCurrentAccount()
4064          folder = self.getCurrentFolder()
4065          # refresh count indicator
4066          self.propagateUnreadCount(folder)
4067          self.loadMessagelist(
4068              messagelist, account, folder,
4069              self.getCurrentSearchOption(), self.getCurrentSearchLine())
4070  
4071      def treeWidgetItemChanged(self, item, column):
4072          # only for manual edits. automatic edits (setText) are ignored
4073          if column != 0:
4074              return
4075          # only account names of normal addresses (no chans/mailinglists)
4076          if (not isinstance(item, Ui_AddressWidget)) or \
4077                  (not self.getCurrentTreeWidget()) or \
4078                  self.getCurrentTreeWidget().currentItem() is None:
4079              return
4080          # not visible
4081          if (not self.getCurrentItem()) or (not isinstance(self.getCurrentItem(), Ui_AddressWidget)):
4082              return
4083          # only currently selected item
4084          if item.address != self.getCurrentAccount():
4085              return
4086          # "All accounts" can't be renamed
4087          if item.type == AccountMixin.ALL:
4088              return
4089  
4090          newLabel = str(item.text(0), 'utf-8', 'ignore')
4091          oldLabel = item.defaultLabel()
4092  
4093          # unchanged, do not do anything either
4094          if newLabel == oldLabel:
4095              return
4096  
4097          # recursion prevention
4098          if self.recurDepth > 0:
4099              return
4100  
4101          self.recurDepth += 1
4102          if item.type == AccountMixin.NORMAL or item.type == AccountMixin.MAILINGLIST:
4103              self.rerenderComboBoxSendFromBroadcast()
4104          if item.type == AccountMixin.NORMAL or item.type == AccountMixin.CHAN:
4105              self.rerenderComboBoxSendFrom()
4106          self.rerenderMessagelistFromLabels()
4107          if item.type != AccountMixin.SUBSCRIPTION:
4108              self.rerenderMessagelistToLabels()
4109          if item.type in (AccountMixin.NORMAL, AccountMixin.CHAN, AccountMixin.SUBSCRIPTION):
4110              self.rerenderAddressBook()
4111          self.recurDepth -= 1
4112  
4113      def tableWidgetInboxItemClicked(self):
4114          messageTextedit = self.getCurrentMessageTextedit()
4115          if not messageTextedit:
4116              return
4117  
4118          msgid = self.getCurrentMessageId()
4119          folder = self.getCurrentFolder()
4120          if msgid:
4121              queryreturn = sqlQuery(
4122                  '''SELECT message FROM %s WHERE %s=?''' % (
4123                      ('sent', 'ackdata') if folder == 'sent'
4124                      else ('inbox', 'msgid')
4125                  ), msgid
4126              )
4127  
4128          try:
4129              message = queryreturn[-1][0]
4130          except NameError:
4131              message = ""
4132          except IndexError:
4133              message = _translate(
4134                  "MainWindow",
4135                  "Error occurred: could not load message from disk."
4136              )
4137          else:
4138              tableWidget = self.getCurrentMessagelist()
4139              currentRow = tableWidget.currentRow()
4140              # refresh
4141              if tableWidget.item(currentRow, 0).unread is True:
4142                  self.updateUnreadStatus(tableWidget, currentRow, msgid)
4143              # propagate
4144              if folder != 'sent' and sqlExecute(
4145                  '''UPDATE inbox SET read=1 WHERE msgid=? AND read=0''',
4146                  msgid
4147              ) > 0:
4148                  self.propagateUnreadCount()
4149  
4150          messageTextedit.setCurrentFont(QtGui.QFont())
4151          messageTextedit.setTextColor(QtGui.QColor())
4152          messageTextedit.setContent(message)
4153  
4154      def tableWidgetAddressBookItemChanged(self, item):
4155          if item.type == AccountMixin.CHAN:
4156              self.rerenderComboBoxSendFrom()
4157          self.rerenderMessagelistFromLabels()
4158          self.rerenderMessagelistToLabels()
4159          completerList = self.ui.lineEditTo.completer().model().stringList()
4160          for i in range(len(completerList)):
4161              if str(completerList[i]).endswith(" <" + item.address + ">"):
4162                  completerList[i] = item.label + " <" + item.address + ">"
4163          self.ui.lineEditTo.completer().model().setStringList(completerList)
4164  
4165      def tabWidgetCurrentChanged(self, n):
4166          if n == self.ui.tabWidget.indexOf(self.ui.networkstatus):
4167              self.ui.networkstatus.startUpdate()
4168          else:
4169              self.ui.networkstatus.stopUpdate()
4170  
4171      def writeNewAddressToTable(self, label, address, streamNumber):
4172          self.rerenderTabTreeMessages()
4173          self.rerenderTabTreeSubscriptions()
4174          self.rerenderTabTreeChans()
4175          self.rerenderComboBoxSendFrom()
4176          self.rerenderComboBoxSendFromBroadcast()
4177          self.rerenderAddressBook()
4178  
4179      def updateStatusBar(self, data):
4180          try:
4181              message, option = data
4182          except ValueError:
4183              option = 0
4184              message = data
4185          except TypeError:
4186              logger.debug(
4187                  'Invalid argument for updateStatusBar!', exc_info=True)
4188  
4189          if message != "":
4190              logger.info('Status bar: ' + message)
4191  
4192          if option == 1:
4193              self.statusbar.addImportant(message)
4194          else:
4195              self.statusbar.showMessage(message, 10000)
4196  
4197      def resetNamecoinConnection(self):
4198          namecoin.ensureNamecoinOptions()
4199          self.namecoin = namecoin.namecoinConnection()
4200  
4201          # Check to see whether we can connect to namecoin.
4202          # Hide the 'Fetch Namecoin ID' button if we can't.
4203          if config.safeGetBoolean(
4204              'bitmessagesettings', 'dontconnect'
4205          ) or self.namecoin.test()[0] == 'failed':
4206              logger.warning(
4207                  'There was a problem testing for a Namecoin daemon.'
4208                  ' Hiding the Fetch Namecoin ID button')
4209              self.ui.pushButtonFetchNamecoinID.hide()
4210          else:
4211              self.ui.pushButtonFetchNamecoinID.show()
4212  
4213      def initSettings(self):
4214          self.loadSettings()
4215          for attr, obj in self.ui.__dict__.items():
4216              if hasattr(obj, "__class__") and \
4217                      isinstance(obj, settingsmixin.SettingsMixin):
4218                  loadMethod = getattr(obj, "loadSettings", None)
4219                  if callable(loadMethod):
4220                      obj.loadSettings()
4221  
4222  
4223  app = None
4224  myapp = None
4225  
4226  
4227  class BitmessageQtApplication(QtGui.QApplication):
4228      """
4229      Listener to allow our Qt form to get focus when another instance of the
4230      application is open.
4231  
4232      Based off this nice reimplmentation of MySingleApplication:
4233      http://stackoverflow.com/a/12712362/2679626
4234      """
4235  
4236      # Unique identifier for this application
4237      uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c'
4238  
4239      @staticmethod
4240      def get_windowstyle():
4241          """Get window style set in config or default"""
4242          return config.safeGet(
4243              'bitmessagesettings', 'windowstyle',
4244              'Windows' if is_windows else 'GTK+'
4245          )
4246  
4247      def __init__(self, *argv):
4248          super(BitmessageQtApplication, self).__init__(*argv)
4249          id = BitmessageQtApplication.uuid
4250  
4251          QtCore.QCoreApplication.setOrganizationName("PyBitmessage")
4252          QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org")
4253          QtCore.QCoreApplication.setApplicationName("pybitmessageqt")
4254  
4255          self.setStyle(self.get_windowstyle())
4256  
4257          font = config.safeGet('bitmessagesettings', 'font')
4258          if font:
4259              # family, size, weight = font.split(',')
4260              family, size = font.split(',')
4261              self.setFont(QtGui.QFont(family, int(size)))
4262  
4263          self.server = None
4264          self.is_running = False
4265  
4266          socket = QLocalSocket()
4267          socket.connectToServer(id)
4268          self.is_running = socket.waitForConnected()
4269  
4270          # Cleanup past crashed servers
4271          if not self.is_running:
4272              if socket.error() == QLocalSocket.ConnectionRefusedError:
4273                  socket.disconnectFromServer()
4274                  QLocalServer.removeServer(id)
4275  
4276          socket.abort()
4277  
4278          # Checks if there's an instance of the local server id running
4279          if self.is_running:
4280              # This should be ignored, singleinstance.py will take care of exiting me.
4281              pass
4282          else:
4283              # Nope, create a local server with this id and assign on_new_connection
4284              # for whenever a second instance tries to run focus the application.
4285              self.server = QLocalServer()
4286              self.server.listen(id)
4287              self.server.newConnection.connect(self.on_new_connection)
4288  
4289          self.setStyleSheet("QStatusBar::item { border: 0px solid black }")
4290  
4291      def __del__(self):
4292          if self.server:
4293              self.server.close()
4294  
4295      def on_new_connection(self):
4296          if myapp:
4297              myapp.appIndicatorShow()
4298  
4299  
4300  def init():
4301      global app
4302      if not app:
4303          app = BitmessageQtApplication(sys.argv)
4304      return app
4305  
4306  
4307  def run():
4308      global myapp
4309      app = init()
4310      myapp = MyForm()
4311  
4312      myapp.appIndicatorInit(app)
4313  
4314      if myapp._firstrun:
4315          myapp.showConnectDialog()  # ask the user if we may connect
4316  
4317  #    try:
4318  #        if config.get('bitmessagesettings', 'mailchuck') < 1:
4319  #            myapp.showMigrationWizard(config.get('bitmessagesettings', 'mailchuck'))
4320  #    except:
4321  #        myapp.showMigrationWizard(0)
4322  
4323      # only show after wizards and connect dialogs have completed
4324      if not config.getboolean('bitmessagesettings', 'startintray'):
4325          myapp.show()
4326          QtCore.QTimer.singleShot(
4327              30000, lambda: myapp.setStatusIcon(state.statusIconColor))
4328  
4329      app.exec_()