class ChatCore: def __init__(self): self.message_references = [] self._communities = {} self._communities_listwidgets = {} self._square_search_dialog = None self._message_attachment = None ################################## #Slots: ################################## #TODO: Refactor the 3 search functions to 3 small ones and a generic one as they are basically the same. def onMemberSearchUpdate(self, cache, event): #TODO: print "Received member search update" #TODO: Deal with status changes and notify user when search is done. if self._member_search_dialog: self._member_search_dialog.clearResultsList() for suggestion in cache.suggestions: member = suggestion.hit if suggestion.state == 'done': self._member_search_dialog.addResult(member.alias, member.thumbnail_hash) if event == "finished": self._member_search_dialog.onSearchFinished() else: print "But the search window doesn't exist, dropping it..." def onSquareSearchUpdate(self, cache, event): #TODO: print "Received Square search update" #TODO: Deal with status changes and notify user when search is done. if self._square_search_dialog: self._square_search_dialog.clearResultsList() for suggestion in cache.suggestions: square = suggestion.hit if suggestion.state == 'done': self._square_search_dialog.addResult(square) if event == "finished": self._square_search_dialog.onSearchFinished() else: print "But the search window doesn't exist, dropping it..." def onTextSearchUpdate(self, cache, event): #TODO: print "Received text search update" #TODO: Deal with status changes and notify user when search is done. if self._message_search_dialog: self._message_search_dialog.clearResultsList() for suggestion in cache.suggestions: text = suggestion.hit if suggestion.state == 'done': self._message_search_dialog.addResult(text.text, text.member, text.square) if event == "finished": self._message_search_dialog.onSearchFinished() else: print "But the search window doesn't exist, dropping it..." def onTextMessageReceived(self, message): #Put the message in the overview list ChatMessageListItem(parent=self.mainwin.message_list, message=message) while self.mainwin.message_list.count() > 250: print "Deleting A chat message" self.mainwin.message_list.takeItem(0) #Put the message in the square specific list square_list_widget = self._communities_listwidgets[message.square.cid] ChatMessageListItem(parent=square_list_widget, message=message) def onNickChanged(self, *argv, **kwargs): alias = self.mainwin.nick_line.text() print "Alias changed to:", alias if alias and alias != self._config['Member']['Alias']: self._config['Member']['Alias'] = alias self._propagateMemberInfoToAll() self._config.write() else: print "Same or empty nick, doing nothing" def onMessageReadyToSend(self): message = self.mainwin.message_line.text() #TODO: Check if the community where we are sending the message has our member info up to date!! if message: print "Sending message: ", message #Get currently selected community current_item = self.mainwin.squares_list.selectedItems()[0] if type(current_item) is SquareOverviewListItem: square = current_item.square #TODO: Add media_hash support, empty string ATM. media_hash = '' self._tgs.sendText(square, message, media_hash) self.mainwin.message_line.clear() else: msg_box = QtGui.QMessageBox() msg_box.setText("Please, select to which square you want to send the message from the the top-left list first.") msg_box.exec_() else: print "I categorically refuse to send an empty message." def onNewCommunityCreated(self, square): #TODO: We need to update the square list here. print "New square created", square #TODO: We should switch to an MVC widget soon, so we can sort, filter, update, etc easily. list_item = SquareOverviewListItem(parent=self.mainwin.squares_list, square=square) item_index = self.mainwin.squares_list.row(list_item) #Create this square's messages list list_widget = QtGui.QListWidget() #Setup widget properties list_widget.setFrameShape(QtGui.QFrame.NoFrame) list_widget.setFrameShadow(QtGui.QFrame.Plain) list_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) list_widget.setAutoScroll(True) list_widget.setAutoScrollMargin(2) list_widget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) list_widget.setProperty("showDropIndicator", False) list_widget.setDragDropMode(QtGui.QAbstractItemView.NoDragDrop) list_widget.setSelectionMode(QtGui.QAbstractItemView.NoSelection) list_widget.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) list_widget.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) list_widget.setMovement(QtGui.QListView.Snap) list_widget.setProperty("isWrapping", False) list_widget.setSpacing(2) list_widget.setWordWrap(True) #Scroll to bottom at each new message insertion message_model = list_widget.model() message_model.rowsInserted.connect(list_widget.scrollToBottom) self.mainwin.messages_stack.insertWidget(item_index, list_widget) self.mainwin.messages_stack.setCurrentIndex(item_index) list_item.setSelected(True) self._communities_listwidgets[square.cid]=list_widget self._communities[square.cid] = square #Set member info for this square self._setMemberInfo(square) #TODO: Put this on the widget constructor, and remove it from here and onNewPreviewCommunityCreated square.events.connect(square.events, QtCore.SIGNAL('squareInfoUpdated'), list_item.onInfoUpdated) square.events.connect(square.events, QtCore.SIGNAL('messageReceived'), self.onTextMessageReceived) def onJoinSuggestedCommunity(self): #TODO: disable the leave/join buttons if no square is selected print "Joining a new community!" item = self.mainwin.suggested_squares_list.currentItem() if item: square = item.square self._tgs.joinSquare(square) else: msg_box = QtGui.QMessageBox() msg_box.setText("Please, select which square you want to join from the suggested squares list.") msg_box.exec_() def onLeaveCommunity(self): print "leaving community!" #Get currently selected community current_item = self.mainwin.squares_list.currentItem() if type(current_item) is SquareOverviewListItem: square = current_item.square self._tgs.leaveSquare(square) row = self.mainwin.squares_list.currentRow() self.mainwin.squares_list.takeItem(row) #Remove the square reference from the squares list self._communities.pop(current_item.square.cid) else: msg_box = QtGui.QMessageBox() msg_box.setText("Please, select which square you want to leave from the top-left list first.") msg_box.exec_() def onNewHotCommunitiesAvailable(self, squares, texts): print "New suggestions arrived", squares, texts self.mainwin.suggested_squares_list.clear() for square in squares: list_item = SquareOverviewListItem(parent=self.mainwin.suggested_squares_list, square=square) list_item.square = square def onCreateSquareBtnPushed(self): self.mainwin.createSquare_btn.setEnabled(False) self._square_edit_dialog = SquareEditDialog() self._square_edit_dialog.squareInfoReady.connect(self.onSquareCreateDialogFinished) self._square_edit_dialog.show() def onSquareCreateDialogFinished(self): square_info = self._square_edit_dialog.getSquareInfo() self._tgs.createNewSquare(square_info) self._square_edit_dialog = None self.mainwin.createSquare_btn.setEnabled(True) def onSearchSquareClicked(self): self.mainwin.search_square_btn.setEnabled(False) self._square_search_dialog = SquareSearchDialog() self._square_search_dialog.rejected.connect(self.onSquareSearchDialogClosed) self._square_search_dialog.onSearchRequested.connect(self._tgs.startNewSquareSearch) self._square_search_dialog.onJoinSquareRequested.connect(self._tgs.joinSquare) self._square_search_dialog.show() def onSquareSearchDialogClosed(self): self.mainwin.search_square_btn.setEnabled(True) def onSearchMessageClicked(self): self.mainwin.search_message_btn.setEnabled(False) self._message_search_dialog = MessageSearchDialog() self._message_search_dialog.rejected.connect(self.onMessageSearchDialogClosed) self._message_search_dialog.onSearchRequested.connect(self._tgs.startNewTextSearch) self._message_search_dialog.show() def onMessageSearchDialogClosed(self): self.mainwin.search_message_btn.setEnabled(True) def onSearchMemberClicked(self): self.mainwin.search_member_btn.setEnabled(False) self._member_search_dialog = MemberSearchDialog() self._member_search_dialog.rejected.connect(self.onMemberSearchDialogClosed) self._member_search_dialog.onSearchRequested.connect(self._tgs.startNewMemberSearch) self._member_search_dialog.show() def onMemberSearchDialogClosed(self): self.mainwin.search_member_btn.setEnabled(True) def onThumbnailButtonPressed(self): fileName = QtGui.QFileDialog.getOpenFileName(self.mainwin, "Select your avatar", "", "Image Files (*.png *.jpg *.bmp *.gif)" ) image = QtGui.QPixmap(fileName) if image.width() > image.height(): image = image.scaledToWidth(64) else: image = image.scaledToHeight(64) self.mainwin.avatar_btn.setIcon(QtGui.QIcon(image)) thumb_data = QtCore.QBuffer() thumb_data.open(thumb_data.ReadWrite) image.save(thumb_data, 'PNG') self._config['Member']['Thumbnail'] = thumb_data.buffer().toBase64() self._config.write() def onAttachButtonToggled(self, status): if status: self._message_attachment = QtGui.QFileDialog.getOpenFileName(self.mainwin, "Attach file to message", "", "") self.mainwin.attach_btn.setToolTip(self._message_attachment) else: self._message_attachment = None self.mainwin.attach_btn.setToolTip('') ################################## #Public Methods ################################## def run(self): #Read config file self._getConfig() #Setup TGS core self._tgs = TGS(self._workdir) self._tgs.memberSearchUpdate.connect(self.onMemberSearchUpdate) self._tgs.squareSearchUpdate.connect(self.onSquareSearchUpdate) self._tgs.textSearchUpdate.connect(self.onTextSearchUpdate) #Setup QT main window self.app = QtGui.QApplication(sys.argv) self.mainwin = MainWin() #Set configurable values self.mainwin.nick_line.setText(self._config['Member']['Alias']) thumb_data = QtCore.QBuffer() thumb_data.open(thumb_data.ReadWrite) thumb_bytes = QtCore.QByteArray.fromBase64(self._config['Member']['Thumbnail']) pixmap = QtGui.QPixmap() pixmap.loadFromData(thumb_bytes, 'PNG') self.mainwin.avatar_btn.setIcon(QtGui.QIcon(pixmap)) #Connect main window signals self.mainwin.nick_line.editingFinished.connect(self.onNickChanged) self.mainwin.avatar_btn.clicked.connect(self.onThumbnailButtonPressed) self.mainwin.message_line.returnPressed.connect( self.onMessageReadyToSend) self.mainwin.message_send_btn.clicked.connect( self.onMessageReadyToSend) self.mainwin.attach_btn.toggled.connect(self.onAttachButtonToggled) self.mainwin.join_square_btn.clicked.connect(self.onJoinSuggestedCommunity) self.mainwin.leave_square_btn.clicked.connect(self.onLeaveCommunity) self.mainwin.createSquare_btn.clicked.connect(self.onCreateSquareBtnPushed) self.mainwin.search_square_btn.clicked.connect(self.onSearchSquareClicked) self.mainwin.search_message_btn.clicked.connect(self.onSearchMessageClicked) self.mainwin.search_member_btn.clicked.connect(self.onSearchMemberClicked) #Hide the tools panel self.mainwin.tools_grp.hide() #TODO: Refactor this to put it in TGS class #Connect global events global_events.qt.newHotCommunitiesAvailable.connect(self.onNewHotCommunitiesAvailable) global_events.qt.newCommunityCreated.connect(self.onNewCommunityCreated) #global_events.qt.newPreviewCommunityCreated.connect( # self.onNewPreviewCommunityCreated) #Setup dispersy threads self._tgs.setupThreads() #Show the main window self.mainwin.show() #Start QT's event loop self.app.exec_() #Destroy dispersy threads before exiting self._tgs.stopThreads() ################################## #Private Methods ################################## def _getConfig(self): current_os = sys.platform if len(sys.argv) > 1 and os.path.exists(sys.argv[1]): config_path = unicode(sys.argv[1]) elif current_os in ('win32','cygwin'): config_path = os.path.join(os.environ['AppData'], 'TheGlobalSquare') elif current_os.startswith('linux'): config_path = os.path.join(os.environ['HOME'], '.config', 'TheGlobalSquare') elif current_os == 'darwin': config_path = os.path.join('/Users', os.environ['USER'], 'Library', 'Preferences', 'TheGlobalSquare') else: print "I don't know where to store my config in this operating system and didn't receive an existing dir as first argument. (%s)\nExiting." % current_os sys.exit(10) #Create app data dir if it doesn't exist if not os.path.exists(config_path): os.makedirs(config_path) config_file_path = os.path.join(config_path, CONFIG_FILE_NAME) config = ConfigObj(config_file_path, encoding='utf-8') if not os.path.exists(config_file_path): #Set default values config['Member'] = { 'Alias': u'Anon', 'Thumbnail': '' } config.write() self._workdir = unicode(config_path) self._config = config def _propagateMemberInfoToAll(self): #TODO: Check if the community has up to date info before sending unnecessary updates for community in self._communities.itervalues(): self._setMemberInfo(community) def _setMemberInfo(self, community): alias = self._config['Member']['Alias'] thumbnail = '' #str(self._config['Member']['Thumbnail']) #TODO: Setup this correctly when swift gets integrated self._tgs.setMemberInfo(community, alias, thumbnail)