def __init__(self, tweet=None, without=(), parent=None): super(SingleTweetWidget, self).__init__(parent) self.errorWindow = APIErrorWindow(self) self.tweet = tweet self.client = const.client self.without = without self.setObjectName("SingleTweetWidget") try: self.setupUi() except RuntimeError: return self.fetcher = AsyncFetcher("".join( (cache_path, str(WeRuntimeInfo()["uid"])))) self.download_lock = False self.__favorite_queue = []
def __init__(self, parent=None): super(WAsyncLabel, self).__init__(parent) self._url = "" self._image = None self.fetcher = AsyncFetcher("".join( (down_path, str(WeRuntimeInfo()["uid"])))) busyIconPixmap = WObjectCache().open(QtGui.QPixmap, ":/IMG/img/busy.gif") self.minimumImageHeight = busyIconPixmap.height() self.minimumImageWidth = busyIconPixmap.width() self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.contextMenu)
def _setupUserTab(self, uid, switch=True, myself=False): index = self._getSameTab("uid", uid) if index: if switch: self.tabWidget.setCurrentIndex(index) return view = TweetListWidget() _timeline = TweetUserModel(self.client.statuses.user_timeline, uid, view) timeline = TweetFilterModel(_timeline) timeline.setModel(_timeline) tab = self._setupCommonTab(timeline, view, switch, myself) def setAvatar(f): self._setTabIcon(tab, WObjectCache().open(QtGui.QPixmap, f)) fetcher = AsyncFetcher("".join((path.cache_path, str(self.info["uid"])))) fetcher.addTask(self.client.users.show.get(uid=uid)["profile_image_url"], setAvatar)
def __init__(self, tweet=None, without=[], parent=None): super(SingleTweetWidget, self).__init__(parent) self.errorWindow = APIErrorWindow(self) self._gif_list = {} self.tweet = tweet self.client = const.client self.without = without self.setObjectName("SingleTweetWidget") self.setupUi() self.fetcher = AsyncFetcher("".join((cache_path, str(WeRuntimeInfo()["uid"])))) self.download_lock = False self.__favorite_queue = []
def __init__(self, parent=None): super(WAsyncLabel, self).__init__(parent) self._url = "" self._image = None self.fetcher = AsyncFetcher("".join((down_path, str(WeRuntimeInfo()["uid"])))) busyIconPixmap = WObjectCache().open(QtGui.QPixmap, ":/IMG/img/busy.gif") self.minimumImageHeight = busyIconPixmap.height() self.minimumImageWidth = busyIconPixmap.width() self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.contextMenu)
def _setupUserTab(self, uid, switch=True, myself=False): index = self._getSameTab("uid", uid) if index: if switch: self.tabWidget.setCurrentIndex(index) return view = TweetListWidget() _timeline = TweetUserModel(self.client.api("statuses/user_timeline"), uid, view) timeline = TweetFilterModel(_timeline) timeline.setModel(_timeline) tab = self._setupCommonTab(timeline, view, switch, myself) def setAvatar(f): self._setTabIcon(tab, WObjectCache().open(QtGui.QPixmap, f)) fetcher = AsyncFetcher("".join( (path.cache_path, str(self.info["uid"])))) fetcher.addTask( self.client.api("users/show").get(uid=uid)["profile_image_url"], setAvatar)
class WAsyncLabel(WImageLabel): clicked = QtCore.pyqtSignal(int) def __init__(self, parent=None): super(WAsyncLabel, self).__init__(parent) self._url = "" self._image = None self.fetcher = AsyncFetcher("".join((down_path, str(WeRuntimeInfo()["uid"])))) busyIconPixmap = WObjectCache().open(QtGui.QPixmap, ":/IMG/img/busy.gif") self.minimumImageHeight = busyIconPixmap.height() self.minimumImageWidth = busyIconPixmap.width() self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.contextMenu) def url(self): return self._url def setBusy(self, busy): if busy: # Hack: #74. # What's wrong with the busyMovie()? To save the memory, # We use a single busyMovie() in the whole program. # If the image downloaded here, we'll stop the movie and the # busyIcon will disappear. But it may start from somewhere else. # The the busyIcon appear again unexpectedly. # The quick fix is disconnecting the signal/slot connection # when we stop the movie. self.animation = WObjectCache().open(QtGui.QMovie, ":/IMG/img/busy.gif") self.animation.start() self.animation.frameChanged.connect(self.drawBusyIcon) else: self.clearBusyIcon() @QtCore.pyqtSlot() def drawBusyIcon(self): image = QtGui.QPixmap(self._image) icon = self.animation.currentPixmap() height = (image.height() - icon.height()) / 2 width = (image.width() - icon.width()) / 2 painter = QtGui.QPainter(image) painter.drawPixmap(width, height, icon) painter.end() super(WAsyncLabel, self).setPixmap(image) def clearBusyIcon(self): self.animation.stop() self.animation.frameChanged.disconnect(self.drawBusyIcon) super(WAsyncLabel, self).setPixmap(self._image) def _setPixmap(self, path): _image = QtGui.QPixmap(path) minimalHeight = self.minimumImageHeight minimalWidth = self.minimumImageWidth if _image.height() < minimalHeight or _image.width() < minimalWidth: if _image.height() > minimalHeight: height = _image.height() else: height = minimalHeight if _image.width() > minimalWidth: width = _image.width() else: width = minimalWidth image = QtGui.QPixmap(width, height) painter = QtGui.QPainter(image) path = QtGui.QPainterPath() path.addRect(0, 0, width, height) painter.fillPath(path, QtGui.QBrush(QtCore.Qt.gray)) painter.drawPixmap((width - _image.width()) / 2, (height - _image.height()) / 2, _image) painter.end() else: image = _image self._image = image super(WAsyncLabel, self).setPixmap(image) def setPixmap(self, url): super(WAsyncLabel, self).setMovie( WObjectCache().open(QtGui.QMovie, ":/IMG/img/busy.gif") ) self.start() if not ("http" in url): self._setPixmap(url) return self._url = url self._fetch() def _fetch(self): self.fetcher.addTask(self._url, self.setPixmap) def mouseReleaseEvent(self, e): if e.button() == QtCore.Qt.LeftButton and self._image: self.clicked.emit(QtCore.Qt.LeftButton) elif e.button() == QtCore.Qt.MiddleButton and self._image: self.clicked.emit(QtCore.Qt.MiddleButton) def contextMenu(self, pos): if not self._image: return saveAction = QtGui.QAction(self) saveAction.setText(self.tr("&Save")) saveAction.triggered.connect(self.save) menu = QtGui.QMenu() menu.addAction(saveAction) menu.exec(self.mapToGlobal(pos)) def save(self): file = QtGui.QFileDialog.getOpenFileName(self, self.tr("Choose a path")) self._image.save(file)
class SingleTweetWidget(QtGui.QFrame): userClicked = QtCore.pyqtSignal(UserItem, bool) tagClicked = QtCore.pyqtSignal(str, bool) deleteReturn = QtCore.pyqtSignal(bool) MENTIONS_RE = re.compile('(@[-a-zA-Z0-9_\u4e00-\u9fa5]+)') SINA_URL_RE = re.compile(r"(http://t.cn/[a-zA-Z0-9]{5,7})") HASHTAG_RE = re.compile("(#.*?#)") def __init__(self, tweet=None, without=(), parent=None): super(SingleTweetWidget, self).__init__(parent) self.errorWindow = APIErrorWindow(self) self.tweet = tweet self.client = const.client self.without = without self.setObjectName("SingleTweetWidget") try: self.setupUi() except RuntimeError: return self.fetcher = AsyncFetcher("".join((cache_path, str(WeRuntimeInfo()["uid"])))) self.download_lock = False self.__favorite_queue = [] def setupUi(self): self.horizontalLayout = QtGui.QHBoxLayout(self) self.horizontalLayout.setMargin(0) self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout_2 = QtGui.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") reason = self.tweet.author.verify_reason if self.tweet.author.verify_type == "personal": self.avatar = WAvatarLabel(WAvatarLabel.PERSONAL_VERIFY, reason) elif self.tweet.author.verify_type == "organization": self.avatar = WAvatarLabel(WAvatarLabel.ORGANIZATION_VERIFY, reason) else: self.avatar = WAvatarLabel(WAvatarLabel.NO_VERIFY) self.avatar.setObjectName("avatar") self.avatar.setPixmap(self.tweet.author.avatar) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.avatar.sizePolicy().hasHeightForWidth()) self.avatar.setSizePolicy(sizePolicy) self.avatar.setAlignment(QtCore.Qt.AlignTop) self.avatar.clicked.connect(self._userClicked) self.verticalLayout_2.addWidget(self.avatar) self.time = SimpleLabel() self.time.setObjectName("time") sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.time.sizePolicy().hasHeightForWidth()) self.time.setSizePolicy(sizePolicy) self.verticalLayout_2.addWidget(self.time) self.verticalLayout_2.setAlignment(QtCore.Qt.AlignTop) self.horizontalLayout.addLayout(self.verticalLayout_2) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.username = QtGui.QLabel(self) self.username.setObjectName("username") self.username.setAlignment(QtCore.Qt.AlignTop) self.username.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) self.verticalLayout.addWidget(self.username) self.tweetText = WTweetLabel(self) self.tweetText.setObjectName("tweetText") self.tweetText.setAlignment(QtCore.Qt.AlignTop) self.tweetText.userClicked.connect(self._userTextClicked) self.tweetText.tagClicked.connect(self._tagClicked) self.verticalLayout.addWidget(self.tweetText) if self.tweet.thumbnail_pic and ("image" not in self.without): self.imageWidget = self._createImageLabel(self.tweet.thumbnail_pic) self.verticalLayout.addWidget(self.imageWidget) if self.tweet.original and ("original" not in self.without): self.originalLabel = self._createOriginalLabel() self.verticalLayout.addWidget(self.originalLabel) self.horizontalLayout.setStretch(1, 1) self.horizontalLayout.setStretch(2, 10) self.counterHorizontalLayout = QtGui.QHBoxLayout() self.counterHorizontalLayout.setObjectName("counterhorizontalLayout") self.horizontalSpacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.counterHorizontalLayout.addItem(self.horizontalSpacer) if WeRuntimeInfo().get("uid") == self.tweet.author.id: self.delete = self._createDeleteLabel() self.counterHorizontalLayout.addWidget(self.delete) if not (self.tweet.type == TweetItem.COMMENT): self.client = QtGui.QLabel() self.client.setText(self.tr("From: %s") % self.tweet.source) self.client.linkActivated.connect(lambda link: openLink(link)) self.counterHorizontalLayout.addWidget(self.client) self.retweet = self._createRetweetLabel() self.counterHorizontalLayout.addWidget(self.retweet) self.comment = self._createCommentLabel() self.counterHorizontalLayout.addWidget(self.comment) self.favorite = self._createFavoriteLabel() self.counterHorizontalLayout.addWidget(self.favorite) self.counterHorizontalLayout.setAlignment(QtCore.Qt.AlignTop) elif self.tweet.type == TweetItem.COMMENT: self.reply = self._createReplyLabel() self.counterHorizontalLayout.addWidget(self.reply) self.verticalLayout.addLayout(self.counterHorizontalLayout) self.horizontalLayout.addLayout(self.verticalLayout) self.setStyleSheet(""" QFrame#SingleTweetWidget { border-bottom: 2px solid palette(highlight); border-radius: 0px; padding: 2px; } """) self.username.setText(" " + self.tweet.author.name) text = QtCore.Qt.escape(self.tweet.text) text = self._create_mentions(text) text = self._create_html_url(text) text = self._create_hashtag(text) text = self._create_smiles(text) self.tweetText.setHtml(text) self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self._update_time) self._update_time() def _setup_timer(self): self.timer.stop() passedSeconds = self.tweet.passedSeconds if passedSeconds < 60: self.timer.start(1 * 1000) elif passedSeconds < 3600: self.timer.start(60 * 1000) elif passedSeconds < 86400: self.timer.start(60 * 60 * 1000) else: self.timer.start(60 * 60 * 24 * 1000) def _update_time(self): try: if not self.time.visibleRegion() and self.timer.isActive(): # Skip update only when timer is active, insure # at least run once time. return if not self.time.toolTip(): self.time.setToolTip(self.tweet.timestamp) if self.tweet.type != TweetItem.COMMENT: self.time.setText("<a href='%s'>%s</a>" % (self.tweet.url, self.tweet.time)) else: self.time.setText("<a href='%s'>%s</a>" % (self.tweet.original.url, self.tweet.time)) self._setup_timer() except: # Sometimes, user closed the window and the window # has been garbage collected already, but # the timer is still running. It will cause a runtime error pass def _createOriginalLabel(self): widget = QtGui.QWidget(self) widget.setObjectName("originalWidget") widgetLayout = QtGui.QVBoxLayout(widget) widgetLayout.setSpacing(0) widgetLayout.setMargin(0) widgetLayout.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop) frame = QtGui.QFrame() frame.setObjectName("originalFrame") widgetLayout.addWidget(frame) layout = QtGui.QVBoxLayout(frame) layout.setObjectName("originalLayout") layout.setAlignment(QtCore.Qt.AlignTop) textLabel = WTweetLabel() textLabel.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop) textLabel.userClicked.connect(self._userTextClicked) textLabel.tagClicked.connect(self._tagClicked) self.textLabel = textLabel # Hack: save a reference originalItem = self.tweet.original text = QtCore.Qt.escape(originalItem.text) text = self._create_mentions(text) text = self._create_html_url(text) text = self._create_hashtag(text) text = self._create_smiles(text) try: authorName = self._create_mentions("@" + originalItem.author.name) textLabel.setHtml("%s: %s" % (authorName, text)) except: # originalItem.text == This tweet deleted by author textLabel.setHtml(text) layout.addWidget(textLabel) if originalItem.thumbnail_pic: layout.addWidget(self._createImageLabel(originalItem.thumbnail_pic)) counterHorizontalLayout = QtGui.QHBoxLayout() counterHorizontalLayout.setObjectName("counterhorizontalLayout") horizontalSpacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) counterHorizontalLayout.addItem(horizontalSpacer) retweet = WIconLabel(widget) retweet.setObjectName("retweet") retweet.setText(str(originalItem.retweets_count)) retweet.setIcon(":/IMG/img/retweets.png") retweet.clicked.connect(self._original_retweet) counterHorizontalLayout.addWidget(retweet) comment = WIconLabel(widget) comment.setObjectName("comment") comment.setIcon(":/IMG/img/comments.png") comment.setText(str(originalItem.comments_count)) comment.clicked.connect(self._original_comment) counterHorizontalLayout.addWidget(comment) counterHorizontalLayout.setSpacing(6) layout.setMargin(8) layout.setSpacing(0) layout.addLayout(counterHorizontalLayout) frame.setStyleSheet(""" QFrame#originalFrame { border: 2px solid palette(highlight); border-radius: 4px; padding: 2px; } """) return widget def _createImageLabel(self, thumbnail_pic): widget = QtGui.QWidget(self) widget.setObjectName("imageLabel") widgetLayout = QtGui.QVBoxLayout(widget) widgetLayout.setSpacing(0) widgetLayout.setMargin(0) widgetLayout.setAlignment(QtCore.Qt.AlignCenter) frame = QtGui.QFrame() frame.setObjectName("imageFrame") widgetLayout.addWidget(frame) layout = QtGui.QVBoxLayout(frame) layout.setObjectName("imageLayout") self.imageLabel = WSwitchLabel(widget) self.imageLabel.setImagesUrls(thumbnail_pic) self.imageLabel.clicked.connect(self._showFullImage) layout.addWidget(self.imageLabel) widgetLayout.addWidget(frame) return widget def _createFavoriteLabel(self): favorite = WIconLabel(self) favorite.setIcon(":/IMG/img/no_favorites.png") favorite.clicked.connect(self._favorite) return favorite def _createRetweetLabel(self): retweet = WIconLabel(self) retweet.setObjectName("retweet") retweet.setText(str(self.tweet.retweets_count)) retweet.setIcon(":/IMG/img/retweets.png") retweet.clicked.connect(self._retweet) return retweet def _createCommentLabel(self): comment = WIconLabel(self) comment.setObjectName("comment") comment.setIcon(":/IMG/img/comments.png") comment.setText(str(self.tweet.comments_count)) comment.clicked.connect(self._comment) return comment def _createReplyLabel(self): reply = WIconLabel(self) reply.setObjectName("reply") reply.setIcon(":/IMG/img/retweets.png") reply.clicked.connect(self._reply) return reply def _createDeleteLabel(self): delete = WIconLabel(self) delete.setObjectName("delete") delete.setIcon(":/IMG/img/deletes.png") delete.clicked.connect(self._delete) return delete def fetch_open_original_pic(self, thumbnail_pic): """Fetch and open original pic from thumbnail pic url. Pictures will stored in cache directory. If we already have a same name in cache directory, just open it. If we don't, then download it first.""" def open_pic(localfile): start(localfile) self.download_lock = False self.imageLabel.setBusy(False) if self.download_lock: return self.download_lock = True self.imageLabel.setBusy(True) original_pic = thumbnail_pic.replace("thumbnail", "large") # A simple trick ... ^_^ self.fetcher.addTask(original_pic, open_pic) def _showFullImage(self): self.fetch_open_original_pic(self.imageLabel.url()) def _favorite(self): needWorker = False if not self.__favorite_queue: state = not self.tweet.isFavorite() needWorker = True elif not self.__favorite_queue[-1]: state = True else: state = False self.__favorite_queue.append(state) if state: self.favorite.setIcon(":/IMG/img/favorites.png") else: self.favorite.setIcon(":/IMG/img/no_favorites.png") if needWorker: self.__favorite_worker() @async def __favorite_worker(self): while self.__favorite_queue: state = self.__favorite_queue[0] try: self.tweet.setFavorite(state) sleep(0.5) self.__favorite_queue.pop(0) except APIError as e: if e.error_code == 20101: self.tweet.setFavoriteForce(True) elif e.error_code == 20704: self.tweet.setFavoriteForce(True) self._e = e self.__favorite_queue = [] self.errorWindow.raiseException.emit(self._e) return def _retweet(self, tweet=None): if not tweet: tweet = self.tweet self.exec_newpost_window("retweet", tweet) def _comment(self, tweet=None): if not tweet: tweet = self.tweet if tweet.type == TweetItem.COMMENT: self._reply(tweet) return self.exec_newpost_window("comment", tweet) def _reply(self, tweet=None): if not tweet: tweet = self.tweet self.exec_newpost_window("reply", tweet) def _delete(self): @async def do_delete(): try: self.tweet.delete() self.deleteReturn.emit(True) except APIError as e: self.errorWindow.raiseException.emit(e) self.deleteReturn.emit(False) questionDialog = QtGui.QMessageBox.question choice = questionDialog(self, self.tr("Delete?"), self.tr("You can't undo your deletion."), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if choice == QtGui.QMessageBox.No: return self.deleteReturn.connect(lambda state: state and self.remove()) do_delete() def remove(self): # not really remove myself. self.timer.stop() self.hide() def _original_retweet(self): self._retweet(self.tweet.original) def _original_comment(self): self._comment(self.tweet.original) def _create_html_url(self, text): return self.SINA_URL_RE.sub(r"""<a href='\1'>\1</a>""", text) def _create_smiles(self, text): faceModel = FaceModel() for face in faceModel.all_faces(): new_text = text.replace("[%s]" % face.name, '<img src="%s" />' % face.path) if new_text != text: self._create_animation(face.path) text = new_text return text def _create_mentions(self, text): return self.MENTIONS_RE.sub(r"""<a href='mentions:///\1'>\1</a>""", text) def _create_hashtag(self, text): return self.HASHTAG_RE.sub(r"""<a href='hashtag:///\1'>\1</a>""", text) def _create_animation(self, path): movie = WObjectCache().open(WMovie, path) movie.frameChanged.connect(self.drawAnimate, QtCore.Qt.UniqueConnection) movie.start() def drawAnimate(self): sender = self.sender() if (not isinstance(sender, WMovie)) or (not self.tweetText.visibleRegion()): return movie = sender self._addSingleFrame(movie, self.tweetText) if self.tweet.original and ("original" not in self.without): self._addSingleFrame(movie, self.textLabel) def _addSingleFrame(self, movie, textBrowser): document = textBrowser.document() document.addResource(QtGui.QTextDocument.ImageResource, QtCore.QUrl(movie.fileName()), movie.currentPixmap()) # Cause a force refresh textBrowser.update() def exec_newpost_window(self, action, tweet): from NewpostWindow import NewpostWindow try: self.wecase_new = NewpostWindow(action, tweet) self.wecase_new.userClicked.connect(self.userClicked) self.wecase_new.tagClicked.connect(self.tagClicked) self.wecase_new.show() except APIError as e: self.errorWindow.raiseException.emit(e) def _userClicked(self, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True self.userClicked.emit(self.tweet.author, openAtBackend) @async def _userTextClicked(self, user, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True try: self.__userItem = UserItem({"name": user}) except APIError as e: self.errorWindow.raiseException.emit(e) return self.userClicked.emit(self.__userItem, openAtBackend) def _tagClicked(self, tag, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True self.tagClicked.emit(tag, openAtBackend)
class SingleTweetWidget(QtGui.QFrame): userClicked = QtCore.pyqtSignal(UserItem, bool) tagClicked = QtCore.pyqtSignal(str, bool) deleteReturn = QtCore.pyqtSignal(bool) MENTIONS_RE = re.compile('(@[-a-zA-Z0-9_\u4e00-\u9fa5]+)') SINA_URL_RE = re.compile(r"(http://t.cn/[a-zA-Z0-9]{5,7})") HASHTAG_RE = re.compile("(#.*?#)") def __init__(self, tweet=None, without=(), parent=None): super(SingleTweetWidget, self).__init__(parent) self.errorWindow = APIErrorWindow(self) self.tweet = tweet self.client = const.client self.without = without self.setObjectName("SingleTweetWidget") try: self.setupUi() except RuntimeError: return self.fetcher = AsyncFetcher("".join( (cache_path, str(WeRuntimeInfo()["uid"])))) self.download_lock = False self.__favorite_queue = [] def setupUi(self): self.horizontalLayout = QtGui.QHBoxLayout(self) self.horizontalLayout.setMargin(0) self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout_2 = QtGui.QVBoxLayout() self.verticalLayout_2.setObjectName("verticalLayout_2") reason = self.tweet.author.verify_reason if self.tweet.author.verify_type == "personal": self.avatar = WAvatarLabel(WAvatarLabel.PERSONAL_VERIFY, reason) elif self.tweet.author.verify_type == "organization": self.avatar = WAvatarLabel(WAvatarLabel.ORGANIZATION_VERIFY, reason) else: self.avatar = WAvatarLabel(WAvatarLabel.NO_VERIFY) self.avatar.setObjectName("avatar") self.avatar.setPixmap(self.tweet.author.avatar) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.avatar.sizePolicy().hasHeightForWidth()) self.avatar.setSizePolicy(sizePolicy) self.avatar.setAlignment(QtCore.Qt.AlignTop) self.avatar.clicked.connect(self._userClicked) self.verticalLayout_2.addWidget(self.avatar) self.time = SimpleLabel() self.time.setObjectName("time") sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.time.sizePolicy().hasHeightForWidth()) self.time.setSizePolicy(sizePolicy) self.verticalLayout_2.addWidget(self.time) self.verticalLayout_2.setAlignment(QtCore.Qt.AlignTop) self.horizontalLayout.addLayout(self.verticalLayout_2) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.username = QtGui.QLabel(self) self.username.setObjectName("username") self.username.setAlignment(QtCore.Qt.AlignTop) self.username.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) self.verticalLayout.addWidget(self.username) self.tweetText = WTweetLabel(self) self.tweetText.setObjectName("tweetText") self.tweetText.setAlignment(QtCore.Qt.AlignTop) self.tweetText.userClicked.connect(self._userTextClicked) self.tweetText.tagClicked.connect(self._tagClicked) self.verticalLayout.addWidget(self.tweetText) if self.tweet.thumbnail_pic and ("image" not in self.without): self.imageWidget = self._createImageLabel(self.tweet.thumbnail_pic) self.verticalLayout.addWidget(self.imageWidget) if self.tweet.original and ("original" not in self.without): self.originalLabel = self._createOriginalLabel() self.verticalLayout.addWidget(self.originalLabel) self.horizontalLayout.setStretch(1, 1) self.horizontalLayout.setStretch(2, 10) self.counterHorizontalLayout = QtGui.QHBoxLayout() self.counterHorizontalLayout.setObjectName("counterhorizontalLayout") self.horizontalSpacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.counterHorizontalLayout.addItem(self.horizontalSpacer) if WeRuntimeInfo().get("uid") == self.tweet.author.id: self.delete = self._createDeleteLabel() self.counterHorizontalLayout.addWidget(self.delete) if not (self.tweet.type == TweetItem.COMMENT): self.client = QtGui.QLabel() self.client.setText(self.tr("From: %s") % self.tweet.source) self.client.linkActivated.connect(lambda link: openLink(link)) self.counterHorizontalLayout.addWidget(self.client) self.retweet = self._createRetweetLabel() self.counterHorizontalLayout.addWidget(self.retweet) self.comment = self._createCommentLabel() self.counterHorizontalLayout.addWidget(self.comment) self.favorite = self._createFavoriteLabel() self.counterHorizontalLayout.addWidget(self.favorite) self.counterHorizontalLayout.setAlignment(QtCore.Qt.AlignTop) elif self.tweet.type == TweetItem.COMMENT: self.reply = self._createReplyLabel() self.counterHorizontalLayout.addWidget(self.reply) self.verticalLayout.addLayout(self.counterHorizontalLayout) self.horizontalLayout.addLayout(self.verticalLayout) self.setStyleSheet(""" QFrame#SingleTweetWidget { border-bottom: 2px solid palette(highlight); border-radius: 0px; padding: 2px; } """) self.username.setText(" " + self.tweet.author.name) text = QtCore.Qt.escape(self.tweet.text) text = self._create_mentions(text) text = self._create_html_url(text) text = self._create_hashtag(text) text = self._create_smiles(text) self.tweetText.setHtml(text) self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self._update_time) self._update_time() def _setup_timer(self): self.timer.stop() passedSeconds = self.tweet.passedSeconds if passedSeconds < 60: self.timer.start(1 * 1000) elif passedSeconds < 3600: self.timer.start(60 * 1000) elif passedSeconds < 86400: self.timer.start(60 * 60 * 1000) else: self.timer.start(60 * 60 * 24 * 1000) def _update_time(self): try: if not self.time.visibleRegion() and self.timer.isActive(): # Skip update only when timer is active, insure # at least run once time. return if not self.time.toolTip(): self.time.setToolTip(self.tweet.timestamp) if self.tweet.type != TweetItem.COMMENT: self.time.setText("<a href='%s'>%s</a>" % (self.tweet.url, self.tweet.time)) else: self.time.setText("<a href='%s'>%s</a>" % (self.tweet.original.url, self.tweet.time)) self._setup_timer() except: # Sometimes, user closed the window and the window # has been garbage collected already, but # the timer is still running. It will cause a runtime error pass def _createOriginalLabel(self): widget = QtGui.QWidget(self) widget.setObjectName("originalWidget") widgetLayout = QtGui.QVBoxLayout(widget) widgetLayout.setSpacing(0) widgetLayout.setMargin(0) widgetLayout.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop) frame = QtGui.QFrame() frame.setObjectName("originalFrame") widgetLayout.addWidget(frame) layout = QtGui.QVBoxLayout(frame) layout.setObjectName("originalLayout") layout.setAlignment(QtCore.Qt.AlignTop) textLabel = WTweetLabel() textLabel.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop) textLabel.userClicked.connect(self._userTextClicked) textLabel.tagClicked.connect(self._tagClicked) self.textLabel = textLabel # Hack: save a reference originalItem = self.tweet.original text = QtCore.Qt.escape(originalItem.text) text = self._create_mentions(text) text = self._create_html_url(text) text = self._create_hashtag(text) text = self._create_smiles(text) try: authorName = self._create_mentions("@" + originalItem.author.name) textLabel.setHtml("%s: %s" % (authorName, text)) except: # originalItem.text == This tweet deleted by author textLabel.setHtml(text) layout.addWidget(textLabel) if originalItem.thumbnail_pic: layout.addWidget(self._createImageLabel( originalItem.thumbnail_pic)) counterHorizontalLayout = QtGui.QHBoxLayout() counterHorizontalLayout.setObjectName("counterhorizontalLayout") horizontalSpacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) counterHorizontalLayout.addItem(horizontalSpacer) retweet = WIconLabel(widget) retweet.setObjectName("retweet") retweet.setText(str(originalItem.retweets_count)) retweet.setIcon(":/IMG/img/retweets.png") retweet.clicked.connect(self._original_retweet) counterHorizontalLayout.addWidget(retweet) comment = WIconLabel(widget) comment.setObjectName("comment") comment.setIcon(":/IMG/img/comments.png") comment.setText(str(originalItem.comments_count)) comment.clicked.connect(self._original_comment) counterHorizontalLayout.addWidget(comment) counterHorizontalLayout.setSpacing(6) layout.setMargin(8) layout.setSpacing(0) layout.addLayout(counterHorizontalLayout) frame.setStyleSheet(""" QFrame#originalFrame { border: 2px solid palette(highlight); border-radius: 4px; padding: 2px; } """) return widget def _createImageLabel(self, thumbnail_pic): widget = QtGui.QWidget(self) widget.setObjectName("imageLabel") widgetLayout = QtGui.QVBoxLayout(widget) widgetLayout.setSpacing(0) widgetLayout.setMargin(0) widgetLayout.setAlignment(QtCore.Qt.AlignCenter) frame = QtGui.QFrame() frame.setObjectName("imageFrame") widgetLayout.addWidget(frame) layout = QtGui.QVBoxLayout(frame) layout.setObjectName("imageLayout") self.imageLabel = WSwitchLabel(widget) self.imageLabel.setImagesUrls(thumbnail_pic) self.imageLabel.clicked.connect(self._showFullImage) layout.addWidget(self.imageLabel) widgetLayout.addWidget(frame) return widget def _createFavoriteLabel(self): favorite = WIconLabel(self) favorite.setIcon(":/IMG/img/no_favorites.png") favorite.clicked.connect(self._favorite) return favorite def _createRetweetLabel(self): retweet = WIconLabel(self) retweet.setObjectName("retweet") retweet.setText(str(self.tweet.retweets_count)) retweet.setIcon(":/IMG/img/retweets.png") retweet.clicked.connect(self._retweet) return retweet def _createCommentLabel(self): comment = WIconLabel(self) comment.setObjectName("comment") comment.setIcon(":/IMG/img/comments.png") comment.setText(str(self.tweet.comments_count)) comment.clicked.connect(self._comment) return comment def _createReplyLabel(self): reply = WIconLabel(self) reply.setObjectName("reply") reply.setIcon(":/IMG/img/retweets.png") reply.clicked.connect(self._reply) return reply def _createDeleteLabel(self): delete = WIconLabel(self) delete.setObjectName("delete") delete.setIcon(":/IMG/img/deletes.png") delete.clicked.connect(self._delete) return delete def fetch_open_original_pic(self, thumbnail_pic): """Fetch and open original pic from thumbnail pic url. Pictures will stored in cache directory. If we already have a same name in cache directory, just open it. If we don't, then download it first.""" def open_pic(localfile): start(localfile) self.download_lock = False self.imageLabel.setBusy(False) if self.download_lock: return self.download_lock = True self.imageLabel.setBusy(True) original_pic = thumbnail_pic.replace("thumbnail", "large") # A simple trick ... ^_^ self.fetcher.addTask(original_pic, open_pic) def _showFullImage(self): self.fetch_open_original_pic(self.imageLabel.url()) def _favorite(self): needWorker = False if not self.__favorite_queue: state = not self.tweet.isFavorite() needWorker = True elif not self.__favorite_queue[-1]: state = True else: state = False self.__favorite_queue.append(state) if state: self.favorite.setIcon(":/IMG/img/favorites.png") else: self.favorite.setIcon(":/IMG/img/no_favorites.png") if needWorker: self.__favorite_worker() @async def __favorite_worker(self): while self.__favorite_queue: state = self.__favorite_queue[0] try: self.tweet.setFavorite(state) sleep(0.5) self.__favorite_queue.pop(0) except APIError as e: if e.error_code == 20101: self.tweet.setFavoriteForce(True) elif e.error_code == 20704: self.tweet.setFavoriteForce(True) self._e = e self.__favorite_queue = [] self.errorWindow.raiseException.emit(self._e) return def _retweet(self, tweet=None): if not tweet: tweet = self.tweet self.exec_newpost_window("retweet", tweet) def _comment(self, tweet=None): if not tweet: tweet = self.tweet if tweet.type == TweetItem.COMMENT: self._reply(tweet) return self.exec_newpost_window("comment", tweet) def _reply(self, tweet=None): if not tweet: tweet = self.tweet self.exec_newpost_window("reply", tweet) def _delete(self): @async def do_delete(): try: self.tweet.delete() self.deleteReturn.emit(True) except APIError as e: self.errorWindow.raiseException.emit(e) self.deleteReturn.emit(False) questionDialog = QtGui.QMessageBox.question choice = questionDialog(self, self.tr("Delete?"), self.tr("You can't undo your deletion."), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if choice == QtGui.QMessageBox.No: return self.deleteReturn.connect(lambda state: state and self.remove()) do_delete() def remove(self): # not really remove myself. self.timer.stop() self.hide() def _original_retweet(self): self._retweet(self.tweet.original) def _original_comment(self): self._comment(self.tweet.original) def _create_html_url(self, text): return self.SINA_URL_RE.sub(r"""<a href='\1'>\1</a>""", text) def _create_smiles(self, text): faceModel = FaceModel() for face in faceModel.all_faces(): new_text = text.replace("[%s]" % face.name, '<img src="%s" />' % face.path) if new_text != text: self._create_animation(face.path) text = new_text return text def _create_mentions(self, text): return self.MENTIONS_RE.sub(r"""<a href='mentions:///\1'>\1</a>""", text) def _create_hashtag(self, text): return self.HASHTAG_RE.sub(r"""<a href='hashtag:///\1'>\1</a>""", text) def _create_animation(self, path): movie = WObjectCache().open(WMovie, path) try: movie.frameChanged.connect(self.drawAnimate, QtCore.Qt.UniqueConnection) except TypeError: # connected already pass movie.start() def drawAnimate(self): sender = self.sender() if (not isinstance(sender, WMovie)) or (not self.tweetText.visibleRegion()): return movie = sender self._addSingleFrame(movie, self.tweetText) if self.tweet.original and ("original" not in self.without): self._addSingleFrame(movie, self.textLabel) def _addSingleFrame(self, movie, textBrowser): document = textBrowser.document() document.addResource(QtGui.QTextDocument.ImageResource, QtCore.QUrl(movie.fileName()), movie.currentPixmap()) # Cause a force refresh textBrowser.update() def exec_newpost_window(self, action, tweet): from NewpostWindow import NewpostWindow try: self.wecase_new = NewpostWindow(action, tweet) self.wecase_new.userClicked.connect(self.userClicked) self.wecase_new.tagClicked.connect(self.tagClicked) self.wecase_new.show() except APIError as e: self.errorWindow.raiseException.emit(e) def _userClicked(self, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True self.userClicked.emit(self.tweet.author, openAtBackend) @async def _userTextClicked(self, user, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True try: self.__userItem = UserItem({"name": user}) except APIError as e: self.errorWindow.raiseException.emit(e) return self.userClicked.emit(self.__userItem, openAtBackend) def _tagClicked(self, tag, button): openAtBackend = False if button == QtCore.Qt.MiddleButton: openAtBackend = True self.tagClicked.emit(tag, openAtBackend)
class WAsyncLabel(WImageLabel): clicked = QtCore.pyqtSignal(int) def __init__(self, parent=None): super(WAsyncLabel, self).__init__(parent) self._url = "" self._image = None self.fetcher = AsyncFetcher("".join( (down_path, str(WeRuntimeInfo()["uid"])))) busyIconPixmap = WObjectCache().open(QtGui.QPixmap, ":/IMG/img/busy.gif") self.minimumImageHeight = busyIconPixmap.height() self.minimumImageWidth = busyIconPixmap.width() self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.contextMenu) def url(self): return self._url def setBusy(self, busy): if busy: # Hack: #74. # What's wrong with the busyMovie()? To save the memory, # We use a single busyMovie() in the whole program. # If the image downloaded here, we'll stop the movie and the # busyIcon will disappear. But it may start from somewhere else. # The the busyIcon appear again unexpectedly. # The quick fix is disconnecting the signal/slot connection # when we stop the movie. self.animation = WObjectCache().open(QtGui.QMovie, ":/IMG/img/busy.gif") self.animation.start() self.animation.frameChanged.connect(self.drawBusyIcon) else: self.clearBusyIcon() @QtCore.pyqtSlot() def drawBusyIcon(self): image = QtGui.QPixmap(self._image) icon = self.animation.currentPixmap() height = (image.height() - icon.height()) / 2 width = (image.width() - icon.width()) / 2 painter = QtGui.QPainter(image) painter.drawPixmap(width, height, icon) painter.end() super(WAsyncLabel, self).setPixmap(image) def clearBusyIcon(self): self.animation.stop() self.animation.frameChanged.disconnect(self.drawBusyIcon) super(WAsyncLabel, self).setPixmap(self._image) def _setPixmap(self, path): _image = QtGui.QPixmap(path) minimalHeight = self.minimumImageHeight minimalWidth = self.minimumImageWidth if _image.height() < minimalHeight or _image.width() < minimalWidth: if _image.height() > minimalHeight: height = _image.height() else: height = minimalHeight if _image.width() > minimalWidth: width = _image.width() else: width = minimalWidth image = QtGui.QPixmap(width, height) painter = QtGui.QPainter(image) path = QtGui.QPainterPath() path.addRect(0, 0, width, height) painter.fillPath(path, QtGui.QBrush(QtCore.Qt.gray)) painter.drawPixmap((width - _image.width()) / 2, (height - _image.height()) / 2, _image) painter.end() else: image = _image self._image = image super(WAsyncLabel, self).setPixmap(image) def setPixmap(self, url): super(WAsyncLabel, self).setMovie(WObjectCache().open(QtGui.QMovie, ":/IMG/img/busy.gif")) self.start() if not ("http" in url): self._setPixmap(url) return self._url = url self._fetch() def _fetch(self): self.fetcher.addTask(self._url, self.setPixmap) def mouseReleaseEvent(self, e): if e.button() == QtCore.Qt.LeftButton and self._image: self.clicked.emit(QtCore.Qt.LeftButton) elif e.button() == QtCore.Qt.MiddleButton and self._image: self.clicked.emit(QtCore.Qt.MiddleButton) def contextMenu(self, pos): if not self._image: return saveAction = QtGui.QAction(self) saveAction.setText(self.tr("&Save")) saveAction.triggered.connect(self.save) menu = QtGui.QMenu() menu.addAction(saveAction) menu.exec(self.mapToGlobal(pos)) def save(self): file = QtGui.QFileDialog.getOpenFileName(self, self.tr("Choose a path")) self._image.save(file)