Ejemplo n.º 1
0
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)