__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_()