From 502d514f5ea1e555e3d9b701784c10559127e46f Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 20:54:24 +0800 Subject: [PATCH 01/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20Issue=20#31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/wecase.py b/src/wecase.py index 28a907c..9245fba 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -69,6 +69,9 @@ def accept(self, client): if self.chk_Remember.isChecked(): self.passwd[str(self.username)] = str(self.password) self.last_login = str(self.username) + # Because this is a model dislog, + # closeEvent won't emit when we accept() the window, but will + # emit when we reject() the window. self.saveConfig() wecase_main = WeCaseWindow() wecase_main.init_account(client) @@ -122,6 +125,9 @@ def saveConfig(self): self.login_config['last_login'] = self.last_login self.login_config['auto_login'] = str(self.chk_AutoLogin.isChecked()) + with open(config_path, "w+") as config_file: + self.config.write(config_file) + def login(self): self.pushButton_log.setText(self.tr("Login, waiting...")) self.pushButton_log.setEnabled(False) @@ -180,8 +186,12 @@ def openRegisterPage(self): webbrowser.open("http://weibo.com/signup/signup.php") def closeEvent(self, event): - with open(config_path, "w+") as config_file: - self.config.write(config_file) + # HACK: When a user want to close this window, closeEvent will emit. + # But if we don't have closeEvent, Qt will call reject(). We use + # reject() to show the error message, so users will see the error and + # they can not close this window. So just do nothing there to allow + # users to close the window. + pass class WeCaseWindow(QtGui.QMainWindow, Ui_frm_MainWindow): From d07b238493f3abe21ffff727e4aa48aa1e32c5c0 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:08:57 +0800 Subject: [PATCH 02/38] =?UTF-8?q?=E5=8F=96=E6=B6=88=E2=80=9C=E8=AE=B0?= =?UTF-8?q?=E4=BD=8F=E6=88=91=E2=80=9D=E6=97=B6=E5=90=8C=E6=97=B6=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E2=80=9C=E8=87=AA=E5=8A=A8=E7=99=BB=E5=BD=95=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wecase.py b/src/wecase.py index 9245fba..fd7aa45 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -63,7 +63,12 @@ def __init__(self, parent=None): self.loadConfig() self.setupUi(self) self.setupMyUi() + self.setupSignals() + + def setupSignals(self): + # Other singals defined in Desinger. self.loginReturn.connect(self.checkLogin) + self.chk_Remember.clicked.connect(self.uncheckAutoLogin) def accept(self, client): if self.chk_Remember.isChecked(): @@ -182,6 +187,11 @@ def setPassword(self, username): if username: self.txt_Password.setText(self.passwd[str(username)]) + @QtCore.pyqtSlot(bool) + def uncheckAutoLogin(self, checked): + if not checked: + self.chk_AutoLogin.setChecked(False) + def openRegisterPage(self): webbrowser.open("http://weibo.com/signup/signup.php") From 183160357db677239339994a4eba962cf2fc4033 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:20:33 +0800 Subject: [PATCH 03/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E2=80=9C=E6=B3=A8?= =?UTF-8?q?=E9=94=80=E2=80=9D=E7=9A=84=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wecase.py b/src/wecase.py index fd7aa45..68c4c1c 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -81,6 +81,9 @@ def accept(self, client): wecase_main = WeCaseWindow() wecase_main.init_account(client) wecase_main.show() + # Maybe users will logout, so reset the status + self.pushButton_log.setText(self.tr("GO!")) + self.pushButton_log.setEnabled(True) self.done(True) def reject(self): @@ -464,8 +467,10 @@ def showAbout(self): wecase_about.exec_() def logout(self): - wecase_login.exec_() self.close() + # This is a model dialog, if we exec it before we close MainWindow + # MainWindow won't close + wecase_login.exec_() def postTweet(self): wecase_new = NewpostWindow() From ece815ced1707d7b2347653d164724e5d94f32e5 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:23:20 +0800 Subject: [PATCH 04/38] =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E2=80=9C=E8=AE=B0=E4=BD=8F=E6=88=91=E2=80=9D=E5=92=8C=E2=80=9C?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=99=BB=E5=BD=95=E2=80=9D=E7=9A=84=E5=8B=BE?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wecase.py b/src/wecase.py index 68c4c1c..8becd88 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -103,7 +103,6 @@ def setupMyUi(self): self.show() self.txt_Password.setEchoMode(QtGui.QLineEdit.Password) self.cmb_Users.addItem(self.last_login) - self.chk_AutoLogin.setChecked(self.auto_login) for username in list(self.passwd.keys()): if username == self.last_login: @@ -111,9 +110,11 @@ def setupMyUi(self): self.cmb_Users.addItem(username) if self.cmb_Users.currentText(): + self.chk_Remember.setChecked(True) self.setPassword(self.cmb_Users.currentText()) if self.auto_login: + self.chk_AutoLogin.setChecked(self.auto_login) self.login() def loadConfig(self): From d2dfef1851031a5c48bd5fa980667d33e478d48d Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:28:22 +0800 Subject: [PATCH 05/38] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E7=9A=84=E5=B7=A5=E7=A8=8B=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.pro | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/wecase.pro diff --git a/src/wecase.pro b/src/wecase.pro new file mode 100644 index 0000000..9847379 --- /dev/null +++ b/src/wecase.pro @@ -0,0 +1,12 @@ +SOURCES = wecase.py\ + Smiley.py\ + Tweet.py + +FORMS = ui/AboutWindow.ui\ + ui/LoginWindow.ui\ + ui/MainWindow.ui\ + ui/NewpostWindow.ui\ + ui/SettingWindow.ui\ + ui/SmileyWindow.ui + +TRANSLATIONS = locale/WeCase_zh_CN.ts From 2b3f18ad67beb524b6a2cd4c42dbe08095ea20f2 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:29:57 +0800 Subject: [PATCH 06/38] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/WeCase_zh_CN.qm | Bin 5315 -> 5383 bytes locale/WeCase_zh_CN.ts | 60 +++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/locale/WeCase_zh_CN.qm b/locale/WeCase_zh_CN.qm index 31fa38c20ad541cc26eea2d4865fa90a7e9bc727..6b2c11e3caccb136cb22a6d3d731aa6fe47c0d35 100644 GIT binary patch delta 482 zcmX@C*{(G~B(Q*iLEDspK|q>;!34-=UBS~WMJT_Vl?Ua%D})E##njL4`|p{Ca$}jK>0~bTOQW~Ep=wv#(k85f#Dz1?#j(T z`6lMbk`kbyPnhS$wE`V?k@?O4MxfwPg$_ZObsj6j-8j^|w5 ze4vLLC;KwViF32_S^Sv@uM)wiO6oA&s51}$TNX6>7^fopH@dk p!ch*OM;X|FnBO@gKfgFtAyJ_?H!~--k{u|<6AWf<=H=bS4gd^%cIE&8 delta 431 zcmXAlODIHf6vn^*oqK28$IQ5flm%Ig$tYQjhK)z^Ohj48W3rf{ER>0ckn&tHZe*d9 z*NoH@VCf{eKNkL70Ad2%(sA}0}xh#;sihr z|Et;vRH^_!2QElz+yI|g1jO8dx6+(wg8huA`2yIVvq(KYx2=2uO?wkSTmrM)y8vm5 zS)!|elE)llAAl)|dC#i|=H)gY2WXjr+ma6f{BpbA5ammB^&Stw0>0~HyXk@-Z@8kW zP0;ln0Qz+ybJ0Ov4?_0LFVSiR=UO^Zt~D!jirqqFb{bHlnwzDls=jF7LeYr6H|7zD zt9Br3TYkvP#MS5wt2wm07HWc{gURz8|CM_g)A&HE @default - + WeCase 微盒 @@ -87,22 +87,22 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. LoginWindow - + Authorize Failed! 认证失败! - + Check your account and password! 检查您的账号和密码! - + GO! 走起! - + Login, waiting... 登录中,请稍候…… @@ -148,27 +148,27 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. NewpostWindow - + WeCase 微盒 - + Retweet Success! 转发成功! - + Comment Success! 评论成功! - + Reply Success! 回复成功! - + Tweet Success! 发送成功! @@ -178,37 +178,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 图片 (*.png *.jpg *.bmp *.gif) - + Picture 图片 - + Choose a image 选择一张图片 - + Remove the picture 移除图片 - + Text too long! 内容过长! - + Please remove some text. 请删除一些文字。 - + Unknown error! 未知错误! - + Images 图片 @@ -256,6 +256,14 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 评论 + + SmileyWindow + + + Choose a smiley + 选择一个表情 + + TweetItem @@ -287,37 +295,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. WeCaseWindow - + Weibo(%d) 微博(%d) - + @Me(%d) @我(%d) - + Comments(%d) 评论(%d) - + WeCase 微盒 - + Weibo 微博 - + @ME @我 - + Comments 评论 @@ -325,12 +333,12 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. WeSettingsWindow - + %i min %i sec %i 分 %i 秒 - + %i sec %i 秒 From 46aeacb6f79191c8424e05704376474a205e642f Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 14 Apr 2013 21:55:22 +0800 Subject: [PATCH 07/38] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E5=B9=B6=E8=AF=84=E8=AE=BA=EF=BC=8C=E8=AF=84?= =?UTF-8?q?=E8=AE=BA=E5=B9=B6=E8=BD=AC=E5=8F=91=EF=BC=88=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/WeCase_zh_CN.ts | 56 ++++++--- res/ui/NewpostWindow.ui | 247 ++++++++++++++++++++++------------------ src/AboutWindow_ui.py | 4 +- src/LoginWindow_ui.py | 2 +- src/MainWindow_ui.py | 2 +- src/NewpostWindow_ui.py | 37 ++++-- src/SettingWindow_ui.py | 2 +- src/SmileyWindow_ui.py | 2 +- src/wecase.py | 16 ++- src/wecase_rc.py | 2 +- 10 files changed, 226 insertions(+), 144 deletions(-) diff --git a/locale/WeCase_zh_CN.ts b/locale/WeCase_zh_CN.ts index 0bb32b1..c2d6537 100644 --- a/locale/WeCase_zh_CN.ts +++ b/locale/WeCase_zh_CN.ts @@ -3,7 +3,7 @@ @default - + WeCase 微盒 @@ -115,22 +115,22 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 新消息 - + 140 140 - + &Picture 图片(&P) - + &Cancel 取消(&C) - + &Send 发送(&S) @@ -140,35 +140,55 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 表情 - + S&miley 表情(&M) + + + Also: + + + + + Comment + + + + + Repost + + + + + Commmet to Original + + NewpostWindow - + WeCase 微盒 - + Retweet Success! 转发成功! - + Comment Success! 评论成功! - + Reply Success! 回复成功! - + Tweet Success! 发送成功! @@ -178,37 +198,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 图片 (*.png *.jpg *.bmp *.gif) - + Picture 图片 - + Choose a image 选择一张图片 - + Remove the picture 移除图片 - + Text too long! 内容过长! - + Please remove some text. 请删除一些文字。 - + Unknown error! 未知错误! - + Images 图片 diff --git a/res/ui/NewpostWindow.ui b/res/ui/NewpostWindow.ui index 2b89a44..3a2fd5a 100644 --- a/res/ui/NewpostWindow.ui +++ b/res/ui/NewpostWindow.ui @@ -7,7 +7,7 @@ 0 0 562 - 292 + 306 @@ -19,129 +19,160 @@ false - - - - - - - - 0 - 200 - - - - - - - - Arial Black - 14 - - - - Qt::LeftToRight - - - 140 - - - Qt::PlainText - - - Qt::AlignCenter - - - - - - - true - - - QFrame::Sunken - - - - - - + + + + + + 0 + 200 + + + + + + + + Arial Black + 14 + + + + Qt::LeftToRight + + + 140 + + + Qt::PlainText + + + Qt::AlignCenter + + + + + + + true + + + QFrame::Sunken + + + + + + + + + + QLayout::SetFixedSize + + + 0 + - + - Qt::Vertical - - - QSizePolicy::Fixed + Qt::Horizontal - 20 + 40 20 - - - - 0 - 40 - + + + Also: + + + + + + + Comment + + + + + + + Repost + + + + + + + Commmet to Original - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - &Picture - - - - - - - &Cancel - - - - - - - &Send - - - - - - - S&miley - - - - - pushButton_picture - pushButton_cancel - pushButton_send - horizontalSpacer - pushButton + + + + + 0 + 40 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Picture + + + + + + + &Cancel + + + + + + + &Send + + + + + + + S&miley + + + + + pushButton_picture + pushButton_cancel + pushButton_send + horizontalSpacer + pushButton + + diff --git a/src/AboutWindow_ui.py b/src/AboutWindow_ui.py index e482018..fc1c493 100644 --- a/src/AboutWindow_ui.py +++ b/src/AboutWindow_ui.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/AboutWindow.ui' +# Form implementation generated from reading ui file './ui/AboutWindow.ui' # -# Created: Sat Apr 13 20:51:20 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! diff --git a/src/LoginWindow_ui.py b/src/LoginWindow_ui.py index 039c5bf..4e630b2 100644 --- a/src/LoginWindow_ui.py +++ b/src/LoginWindow_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file './ui/LoginWindow.ui' # -# Created: Mon Apr 8 16:40:03 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! diff --git a/src/MainWindow_ui.py b/src/MainWindow_ui.py index 435c52c..d64ebaa 100644 --- a/src/MainWindow_ui.py +++ b/src/MainWindow_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file './ui/MainWindow.ui' # -# Created: Mon Apr 8 16:40:03 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! diff --git a/src/NewpostWindow_ui.py b/src/NewpostWindow_ui.py index 234ef75..18c5a33 100644 --- a/src/NewpostWindow_ui.py +++ b/src/NewpostWindow_ui.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file 'ui/NewpostWindow.ui' +# Form implementation generated from reading ui file './ui/NewpostWindow.ui' # -# Created: Sat Apr 13 20:36:28 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,12 +26,10 @@ def _translate(context, text, disambig): class Ui_NewPostWindow(object): def setupUi(self, NewPostWindow): NewPostWindow.setObjectName(_fromUtf8("NewPostWindow")) - NewPostWindow.resize(562, 292) + NewPostWindow.resize(562, 306) NewPostWindow.setAutoFillBackground(False) NewPostWindow.setProperty("unifiedTitleAndToolBarOnMac", False) - self.gridLayout_3 = QtGui.QGridLayout(NewPostWindow) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout = QtGui.QVBoxLayout(NewPostWindow) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.widget = QtGui.QWidget(NewPostWindow) self.widget.setMinimumSize(QtCore.QSize(0, 200)) @@ -55,8 +53,25 @@ def setupUi(self, NewPostWindow): self.textEdit.setObjectName(_fromUtf8("textEdit")) self.gridLayout.addWidget(self.textEdit, 0, 0, 1, 1) self.verticalLayout.addWidget(self.widget) - spacerItem = QtGui.QSpacerItem(20, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - self.verticalLayout.addItem(spacerItem) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize) + self.horizontalLayout.setContentsMargins(-1, -1, 0, -1) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.label_2 = QtGui.QLabel(NewPostWindow) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.horizontalLayout.addWidget(self.label_2) + self.chk_comment = QtGui.QCheckBox(NewPostWindow) + self.chk_comment.setObjectName(_fromUtf8("chk_comment")) + self.horizontalLayout.addWidget(self.chk_comment) + self.chk_repost = QtGui.QCheckBox(NewPostWindow) + self.chk_repost.setObjectName(_fromUtf8("chk_repost")) + self.horizontalLayout.addWidget(self.chk_repost) + self.chk_comment_original = QtGui.QCheckBox(NewPostWindow) + self.chk_comment_original.setObjectName(_fromUtf8("chk_comment_original")) + self.horizontalLayout.addWidget(self.chk_comment_original) + self.verticalLayout.addLayout(self.horizontalLayout) self.widget_2 = QtGui.QWidget(NewPostWindow) self.widget_2.setMinimumSize(QtCore.QSize(0, 40)) self.widget_2.setObjectName(_fromUtf8("widget_2")) @@ -78,7 +93,7 @@ def setupUi(self, NewPostWindow): self.pushButton.setObjectName(_fromUtf8("pushButton")) self.gridLayout_2.addWidget(self.pushButton, 0, 0, 1, 1) self.verticalLayout.addWidget(self.widget_2) - self.gridLayout_3.addLayout(self.verticalLayout, 0, 0, 1, 1) + self.verticalLayout.setStretch(0, 4) self.retranslateUi(NewPostWindow) QtCore.QObject.connect(self.pushButton_cancel, QtCore.SIGNAL(_fromUtf8("clicked()")), NewPostWindow.close) @@ -93,6 +108,10 @@ def setupUi(self, NewPostWindow): def retranslateUi(self, NewPostWindow): NewPostWindow.setWindowTitle(_translate("NewPostWindow", "New Message", None)) self.label.setText(_translate("NewPostWindow", "140", None)) + self.label_2.setText(_translate("NewPostWindow", "Also:", None)) + self.chk_comment.setText(_translate("NewPostWindow", "Comment", None)) + self.chk_repost.setText(_translate("NewPostWindow", "Repost", None)) + self.chk_comment_original.setText(_translate("NewPostWindow", "Commmet to Original", None)) self.pushButton_picture.setText(_translate("NewPostWindow", "&Picture", None)) self.pushButton_cancel.setText(_translate("NewPostWindow", "&Cancel", None)) self.pushButton_send.setText(_translate("NewPostWindow", "&Send", None)) diff --git a/src/SettingWindow_ui.py b/src/SettingWindow_ui.py index 4016a24..8f8c238 100644 --- a/src/SettingWindow_ui.py +++ b/src/SettingWindow_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file './ui/SettingWindow.ui' # -# Created: Mon Apr 8 16:40:03 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! diff --git a/src/SmileyWindow_ui.py b/src/SmileyWindow_ui.py index 45ea1a5..b03845b 100644 --- a/src/SmileyWindow_ui.py +++ b/src/SmileyWindow_ui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file './ui/SmileyWindow.ui' # -# Created: Mon Apr 8 16:40:03 2013 +# Created: Sun Apr 14 21:55:20 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! diff --git a/src/wecase.py b/src/wecase.py index 8becd88..292cdb0 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -644,6 +644,14 @@ def __init__(self, parent=None, action="new", id=None, cid=None, text=""): def setupMyUi(self): if self.action == "new": self.pushButton_send.clicked.connect(self.send_tweet) + elif self.action == "retweet": + self.chk_repost.setEnabled(False) + elif self.action == "comment": + pass + elif self.action == "reply": + self.chk_repost.setEnabled(False) + self.chk_comment.setEnabled(False) + self.chk_comment_original.setEnabled(False) def mentions_suggest(self, text): ret_users = [] @@ -673,7 +681,8 @@ def send(self): def retweet(self): text = str(self.textEdit.toPlainText()) try: - self.client.statuses.repost.post(id=int(self.id), status=text) + self.client.statuses.repost.post(id=int(self.id), status=text, + is_comment=self.chk_comment.isChecked()+self.chk_comment_original.isChecked()) self.notify.showMessage(self.tr("WeCase"), self.tr("Retweet Success!")) self.sendSuccessful.emit() @@ -684,7 +693,10 @@ def retweet(self): def comment(self): text = str(self.textEdit.toPlainText()) try: - self.client.comments.create.post(id=int(self.id), comment=text) + self.client.comments.create.post(id=int(self.id), comment=text, + comment_ori=self.chk_comment_original.isChecked()) + if self.chk_repost.isChecked(): + self.client.statuses.repost.post(id=int(self.id), status=text) self.notify.showMessage(self.tr("WeCase"), self.tr("Comment Success!")) self.sendSuccessful.emit() diff --git a/src/wecase_rc.py b/src/wecase_rc.py index 3b5dc55..081c32c 100644 --- a/src/wecase_rc.py +++ b/src/wecase_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: 周一 4月 8 16:40:01 2013 +# Created: 周日 4月 14 21:55:18 2013 # by: The Resource Compiler for PyQt (Qt v4.8.4) # # WARNING! All changes made in this file will be lost! From 51633a1a0cb0bb4038fd1932a60615245e319d6e Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 15 Apr 2013 12:11:26 +0800 Subject: [PATCH 08/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20Issue=20#34?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wecase.py b/src/wecase.py index 292cdb0..98fae5c 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -770,7 +770,7 @@ def checkChars(self): if numLens == 140: # you can not send empty tweet self.pushButton_send.setEnabled(False) - elif numLens > 0: + elif numLens >= 0: # length is okay self.label.setStyleSheet("color:black;") self.pushButton_send.setEnabled(True) From 0bff3fd0085468960758f2bcd2b7770815f09d4a Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 15 Apr 2013 16:55:14 +0800 Subject: [PATCH 09/38] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E9=A1=BA=E5=B8=A6?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E3=80=81=E8=BD=AC=E5=8F=91=E7=9A=84=E7=89=B9?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E9=A1=BA=E5=B8=A6=E8=BF=9B=E8=A1=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/wecase.py b/src/wecase.py index 98fae5c..53c520e 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -75,7 +75,7 @@ def accept(self, client): self.passwd[str(self.username)] = str(self.password) self.last_login = str(self.username) # Because this is a model dislog, - # closeEvent won't emit when we accept() the window, but will + # closeEvent won't emit when we accept() the window, but will # emit when we reject() the window. self.saveConfig() wecase_main = WeCaseWindow() @@ -430,18 +430,18 @@ def show_notify(self): if reminds['status'] != 0: # Note: do NOT send notify here, or users will crazy. self.tabTextChanged.emit(0, self.tr("Weibo(%d)") - % reminds['status']) + % reminds['status']) if reminds['mention_status'] and self.remindMentions: msg += "%d unread @ME\n" % reminds['mention_status'] self.tabTextChanged.emit(1, self.tr("@Me(%d)") - % reminds['mention_status']) + % reminds['mention_status']) num_msg += 1 if reminds['cmt'] and self.remindComments: msg += "%d unread comment(s)\n" % reminds['cmt'] - self.tabTextChanged.emit(2, self.tr("Comments(%d)" ) - % reminds['cmt']) + self.tabTextChanged.emit(2, self.tr("Comments(%d)") + % reminds['cmt']) num_msg += 1 if num_msg != 0: @@ -635,6 +635,7 @@ def __init__(self, parent=None, action="new", id=None, cid=None, text=""): self.id = id self.cid = cid self.setupUi(self) + self.setupMyUi() self.textEdit.setText(text) self.textEdit.callback = self.mentions_suggest self.textEdit.mention_flag = "@" @@ -643,20 +644,20 @@ def __init__(self, parent=None, action="new", id=None, cid=None, text=""): def setupMyUi(self): if self.action == "new": - self.pushButton_send.clicked.connect(self.send_tweet) + self.chk_repost.setEnabled(False) + self.chk_comment.setEnabled(False) + self.chk_comment_original.setEnabled(False) elif self.action == "retweet": self.chk_repost.setEnabled(False) elif self.action == "comment": - pass + self.chk_comment.setEnabled(False) elif self.action == "reply": self.chk_repost.setEnabled(False) self.chk_comment.setEnabled(False) - self.chk_comment_original.setEnabled(False) def mentions_suggest(self, text): ret_users = [] try: - print(text) word = re.findall(r'@[-a-zA-Z0-9_\u4e00-\u9fa5]+', text)[-1].replace('@', '') except IndexError: return [] @@ -681,8 +682,9 @@ def send(self): def retweet(self): text = str(self.textEdit.toPlainText()) try: - self.client.statuses.repost.post(id=int(self.id), status=text, - is_comment=self.chk_comment.isChecked()+self.chk_comment_original.isChecked()) + self.client.statuses.repost.post(id=int(self.id), status=text, + is_comment=int((self.chk_comment.isChecked() + + self.chk_comment_original.isChecked() * 2))) self.notify.showMessage(self.tr("WeCase"), self.tr("Retweet Success!")) self.sendSuccessful.emit() @@ -693,8 +695,8 @@ def retweet(self): def comment(self): text = str(self.textEdit.toPlainText()) try: - self.client.comments.create.post(id=int(self.id), comment=text, - comment_ori=self.chk_comment_original.isChecked()) + self.client.comments.create.post(id=int(self.id), comment=text, + comment_ori=int(self.chk_comment_original.isChecked())) if self.chk_repost.isChecked(): self.client.statuses.repost.post(id=int(self.id), status=text) self.notify.showMessage(self.tr("WeCase"), @@ -708,7 +710,10 @@ def reply(self): text = str(self.textEdit.toPlainText()) try: self.client.comments.reply.post(id=int(self.id), cid=int(self.cid), - comment=text) + comment=text, + comment_ori=int(self.chk_comment_original.isChecked())) + if self.chk_repost.isChecked(): + self.client.statuses.repost.post(id=int(self.id), status=text) self.notify.showMessage(self.tr("WeCase"), self.tr("Reply Success!")) self.sendSuccessful.emit() @@ -742,8 +747,8 @@ def addImage(self): self.pushButton_picture.setText(self.tr("Picture")) else: self.image = QtGui.QFileDialog.getOpenFileName(self, - self.tr("Choose a " - "image"), + self.tr("Choose a" + " image"), filter=ACCEPT_TYPE) self.pushButton_picture.setText(self.tr("Remove the picture")) @@ -815,12 +820,12 @@ def setupMyUi(self): def setupModels(self): self.smileyModel = SmileyModel(self) self.smileyModel.init_smileies(myself_path + "./ui/img/smiley", - self.smileyModel, SmileyItem) + self.smileyModel, SmileyItem) self.smileyView.rootContext().setContextProperty("SmileyModel", - self.smileyModel) + self.smileyModel) self.smileyView.rootContext().setContextProperty("parentWindow", self) - self.smileyView.setSource( - QtCore.QUrl.fromLocalFile(myself_path + "/ui/SmileyView.qml")) + self.smileyView.setSource(QtCore.QUrl.fromLocalFile( + myself_path + "/ui/SmileyView.qml")) @QtCore.pyqtSlot(str) def returnSmileyName(self, smileyName): From 1a3b6b918615bfe112657003bf05d871f208f6d3 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Thu, 18 Apr 2013 12:38:39 +0800 Subject: [PATCH 10/38] =?UTF-8?q?=E7=AE=80=E5=8C=96=20Tweet=20=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E4=B8=AD=E8=AE=BE=E7=BD=AE=20Role=20=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Tweet.py | 93 ++++++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 62 deletions(-) diff --git a/src/Tweet.py b/src/Tweet.py index 8f287ad..bf0cc24 100644 --- a/src/Tweet.py +++ b/src/Tweet.py @@ -12,7 +12,7 @@ class TweetModel(QtCore.QAbstractListModel): def __init__(self, prototype, parent=None): QtCore.QAbstractListModel.__init__(self, parent) - self.setRoleNames(prototype.roleNames()) + self.setRoleNames(prototype.roles) self.tweets = [] def appendRow(self, item): @@ -35,17 +35,19 @@ def rowCount(self, parent=QtCore.QModelIndex()): class TweetItem(QtCore.QAbstractItemModel): - typeRole = QtCore.Qt.UserRole + 1 - idRole = QtCore.Qt.UserRole + 2 - authorRole = QtCore.Qt.UserRole + 3 - avatarRole = QtCore.Qt.UserRole + 4 - contentRole = QtCore.Qt.UserRole + 5 - timeRole = QtCore.Qt.UserRole + 6 - originalIdRole = QtCore.Qt.UserRole + 7 - originalContentRole = QtCore.Qt.UserRole + 8 - originalAuthorRole = QtCore.Qt.UserRole + 9 - originalTimeRole = QtCore.Qt.UserRole + 10 - thumbnailPicRole = QtCore.Qt.UserRole + 11 + roles = { + QtCore.Qt.UserRole + 1: "type", + QtCore.Qt.UserRole + 2: "id", + QtCore.Qt.UserRole + 3: "author", + QtCore.Qt.UserRole + 4: "avatar", + QtCore.Qt.UserRole + 5: "content", + QtCore.Qt.UserRole + 6: "time", + QtCore.Qt.UserRole + 7: "original_id", + QtCore.Qt.UserRole + 8: "original_content", + QtCore.Qt.UserRole + 9: "original_author", + QtCore.Qt.UserRole + 10: "original_time", + QtCore.Qt.UserRole + 11: "thumbnail_pic" + } def __init__(self, type="", id="", author="", avatar="", content="", time="", original_id="", original_content="", @@ -53,19 +55,24 @@ def __init__(self, type="", id="", author="", avatar="", content="", parent=None): QtCore.QAbstractItemModel.__init__(self, parent) - self.type = type - self.id = id - self.author = author - self.avatar = avatar - self.content = content - self.time = time - self.original_id = original_id - self.original_content = original_content - self.original_author = original_author - self.original_time = original_time - self.thumbnail_pic = thumbnail_pic + self._data = { + "type": type, + "id": id, + "author": author, + "avatar": avatar, + "content": content, + "time": self.sinceTimeString(time), + "original_id": original_id, + "original_content": original_content, + "original_author": original_author, + "original_time": original_time, + "thumbnail_pic": thumbnail_pic + } def sinceTimeString(self, createTime): + if not createTime: + return + create = time_parser().parse(createTime) create_utc = (create - create.utcoffset()).replace(tzinfo=None) now_utc = datetime.utcnow() @@ -86,43 +93,5 @@ def sinceTimeString(self, createTime): return self.tr("%.0f days ago") % (passedSeconds / 86400) - def roleNames(self): - names = {} - names[self.typeRole] = "type" - names[self.idRole] = "id" - names[self.authorRole] = "author" - names[self.avatarRole] = "avatar" - names[self.contentRole] = "content" - names[self.timeRole] = "time" - names[self.originalIdRole] = "original_id" - names[self.originalContentRole] = "original_content" - names[self.originalAuthorRole] = "original_author" - names[self.originalTimeRole] = "original_time" - names[self.thumbnailPicRole] = "thumbnail_pic" - return names - def data(self, role): - if role == self.typeRole: - return self.type - elif role == self.idRole: - return self.id - elif role == self.authorRole: - return self.author - elif role == self.avatarRole: - return self.avatar - elif role == self.contentRole: - return self.content - elif role == self.timeRole: - return self.sinceTimeString(self.time) - elif role == self.originalIdRole: - return self.original_id - elif role == self.originalContentRole: - return self.original_content - elif role == self.originalAuthorRole: - return self.original_author - elif role == self.originalTimeRole: - return self.original_time - elif role == self.thumbnailPicRole: - return self.thumbnail_pic - else: - return None + return self._data[self.roles[role]] From e1b9781b50d558203974a2e82d2ec43d6a0751cf Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 11:21:04 +0800 Subject: [PATCH 11/38] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 12 ++-- res/ui/TweetList.qml | 18 ++--- src/Tweet.py | 139 ++++++++++++++++++++++++++++----------- src/wecase.py | 71 +------------------- 4 files changed, 119 insertions(+), 121 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 882f8ed..3d84398 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -104,7 +104,7 @@ Item { ButtonImage { id: comment - visible: tweetType != "comment"; + visible: tweetType != 2; buttonImageUrl: "img/small_comment_button.png" pressedButtonImageUrl: "img/small_comment_button_pressed.png" @@ -118,7 +118,7 @@ Item { ButtonImage { id: retweet - visible: tweetType != "comment"; + visible: tweetType != 2; buttonImageUrl: "img/small_retweet_button.png" pressedButtonImageUrl: "img/small_retweet_button_pressed.png" @@ -134,7 +134,7 @@ Item { ButtonImage { id: reply - visible: tweetType == "comment"; + visible: tweetType == 2; buttonImageUrl: "img/small_reply_button.png" pressedButtonImageUrl: "img/small_reply_button_pressed.png" @@ -148,7 +148,7 @@ Item { ButtonImage { id: favorite - visible: tweetType != "comment"; + visible: tweetType != 2; buttonImageUrl: { if (isFavorite) { @@ -181,10 +181,10 @@ Item { id: statusText color: "#333333" text: { - if (tweetType == "tweet" || tweetType == "comment") { + if (tweetType == 0 || tweetType == 2) { return '' + tweetScreenName + ':<\/b>
' + addTags(tweetText) } - else if (tweetType == "retweet") { + else if (tweetType == 1) { return '' + tweetScreenName + ':<\/b>
' + addTags(tweetText) + '
' + '    ' + tweetOriginalName + '<\/b>: ' + addTags(tweetOriginalText) diff --git a/res/ui/TweetList.qml b/res/ui/TweetList.qml index 0718520..47c7331 100644 --- a/res/ui/TweetList.qml +++ b/res/ui/TweetList.qml @@ -34,7 +34,7 @@ Rectangle { delegate: TweetDelegate { function retweet_string() { - if (tweetType == "retweet") { + if (tweetType == 1) { return "//@" + tweetScreenName + ":" + tweetText; } else { @@ -56,12 +56,14 @@ Rectangle { } tweetType: type - tweetScreenName: author - tweetOriginalId: original_id - tweetOriginalName: original_author - tweetOriginalText: original_content - tweetText: content - tweetAvatar: avatar + tweetScreenName: author.name + tweetOriginalId: type != 0 && original.id + + // 不是单条微博,有作者信息(原微博不被删),返回作者姓名;否则返回空字符串 + tweetOriginalName: (type != 0 && original.author && original.author.name) || "" + tweetOriginalText: type != 0 && original.text + tweetText: text + tweetAvatar: author.avatar tweetid: id isOwnTweet: true isNewTweet: true @@ -71,7 +73,7 @@ Rectangle { onFavoriteButtonClicked: favorite() onRetweetButtonClicked: mainWindow.repost(tweetid, retweet_string()) onCommentButtonClicked: mainWindow.comment(tweetid) - onReplyButtonClicked: mainWindow.reply(original_id, tweetid) + onReplyButtonClicked: mainWindow.reply(original.id, tweetid) // not implemented onMoreButtonClicked: console.log("Clicked a user: " + tweetScreenName) diff --git a/src/Tweet.py b/src/Tweet.py index bf0cc24..c97dbc2 100644 --- a/src/Tweet.py +++ b/src/Tweet.py @@ -11,65 +11,131 @@ class TweetModel(QtCore.QAbstractListModel): def __init__(self, prototype, parent=None): - QtCore.QAbstractListModel.__init__(self, parent) + super(TweetModel, self).__init__() self.setRoleNames(prototype.roles) - self.tweets = [] + self._tweets = [] def appendRow(self, item): self.insertRow(self.rowCount(), item) def clear(self): - del self.tweets - self.tweets = [] + self._tweets = [] def data(self, index, role): - return self.tweets[index.row()].data(role) + return self._tweets[index.row()].data(role) def insertRow(self, row, item): self.beginInsertRows(QtCore.QModelIndex(), row, row) - self.tweets.insert(row, item) + self._tweets.insert(row, item) self.endInsertRows() def rowCount(self, parent=QtCore.QModelIndex()): - return len(self.tweets) + return len(self._tweets) -class TweetItem(QtCore.QAbstractItemModel): +class UserItem(QtCore.QObject): + def __init__(self, item, parent=None): + super(UserItem, self).__init__() + self._data = item + + @QtCore.pyqtProperty(str, constant=True) + def id(self): + return self._data.get('idstr') + + @QtCore.pyqtProperty(str, constant=True) + def name(self): + return self._data.get('name') + + @QtCore.pyqtProperty(str, constant=True) + def avatar(self): + return self._data.get('profile_image_url') + + +class TweetItem(QtCore.QObject): + TWEET = 0 + RETWEET = 1 + COMMENT = 2 roles = { QtCore.Qt.UserRole + 1: "type", QtCore.Qt.UserRole + 2: "id", QtCore.Qt.UserRole + 3: "author", - QtCore.Qt.UserRole + 4: "avatar", - QtCore.Qt.UserRole + 5: "content", - QtCore.Qt.UserRole + 6: "time", - QtCore.Qt.UserRole + 7: "original_id", - QtCore.Qt.UserRole + 8: "original_content", - QtCore.Qt.UserRole + 9: "original_author", - QtCore.Qt.UserRole + 10: "original_time", - QtCore.Qt.UserRole + 11: "thumbnail_pic" + QtCore.Qt.UserRole + 4: "time", + QtCore.Qt.UserRole + 5: "text", + QtCore.Qt.UserRole + 6: "original", + QtCore.Qt.UserRole + 7: "thumbnail_pic", + QtCore.Qt.UserRole + 8: "original_pic" } - def __init__(self, type="", id="", author="", avatar="", content="", - time="", original_id="", original_content="", - original_author="", original_time="", thumbnail_pic="", - parent=None): - QtCore.QAbstractItemModel.__init__(self, parent) - - self._data = { - "type": type, - "id": id, - "author": author, - "avatar": avatar, - "content": content, - "time": self.sinceTimeString(time), - "original_id": original_id, - "original_content": original_content, - "original_author": original_author, - "original_time": original_time, - "thumbnail_pic": thumbnail_pic + def __init__(self, item={}, parent=None): + super(TweetItem, self).__init__() + self._data = item + + if not item: + return + + self._roleData = { + "type": self.type, + "id": self.id, + "author": self.author, + "time": self.time, + "text": self.text, + "original": self.original, + "thumbnail_pic": self.thumbnail_pic, + "original_pic": self.original_pic } - def sinceTimeString(self, createTime): + def data(self, key): + return self._roleData[self.roles[key]] + + @QtCore.pyqtProperty(int, constant=True) + def type(self): + if "retweeted_status" in self._data: + return self.RETWEET + elif "status" in self._data: + return self.COMMENT + else: + return self.TWEET + + @QtCore.pyqtProperty(str, constant=True) + def id(self): + return self._data.get('idstr') + + @QtCore.pyqtProperty(QtCore.QObject, constant=True) + def author(self): + if "user" in self._data: + self._user = UserItem(self._data.get('user'), self) + return self._user + else: + return None + + @QtCore.pyqtProperty(str, constant=True) + def time(self): + return self._sinceTimeString(self._data.get('created_at')) + + @QtCore.pyqtProperty(str, constant=True) + def text(self): + return self._data.get('text') + + @QtCore.pyqtProperty(QtCore.QObject, constant=True) + def original(self): + if self.type == self.RETWEET: + self._original = TweetItem(self._data.get('retweeted_status')) + return self._original + elif self.type == self.COMMENT: + self._original = TweetItem(self._data.get('status')) + return self._original + else: + return None + + @QtCore.pyqtProperty(str, constant=True) + def thumbnail_pic(self): + return self._data.get('thumbnail_pic', "") + + @QtCore.pyqtProperty(str, constant=True) + def original_pic(self): + return self._data.get('original_pic') + + def _sinceTimeString(self, createTime): if not createTime: return @@ -92,6 +158,3 @@ def sinceTimeString(self, createTime): return self.tr("%.0f hours ago") % (passedSeconds / 3600) return self.tr("%.0f days ago") % (passedSeconds / 86400) - - def data(self, role): - return self._data[self.roles[role]] diff --git a/src/wecase.py b/src/wecase.py index 53c520e..5d78f04 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -296,75 +296,8 @@ def setupModels(self): self.my_timeline) def get_timeline(self, timeline, model, more=False): - for count, item in enumerate(timeline): - # tweet (default), comment or retweet? - item_type = "tweet" - - # simple tweet or comment - item_id = item['idstr'] - item_author = item['user']['name'] - item_author_avatar = item['user']['profile_image_url'] - item_content = item['text'] - item_content_time = item['created_at'] - - # comment only - try: - item_comment_to_original_id = item['status']['idstr'] - item_type = "comment" - except KeyError: - # not a comment - pass - - # original tweet (if retweeted) - try: - item_original_id = item['retweeted_status']['idstr'] - item_original_content = item['retweeted_status']['text'] - item_original_author = item['retweeted_status']['user']['name'] - item_original_time = item['retweeted_status']['created_at'] - item_type = "retweet" - except KeyError: - # not retweeted - pass - - # thumb pic - try: - item_thumb_pic = None - item_thumb_pic = item['thumbnail_pic'] - except KeyError: - try: - item_thumb_pic = item['retweeted_status']['thumbnail_pic'] - except KeyError: - pass - - # tweet - tweet = TweetItem(type=item_type, id=item_id, author=item_author, - avatar=item_author_avatar, content=item_content, - time=item_content_time) - - if item_type == "comment": - # comment - tweet = TweetItem(type=item_type, id=item_id, - author=item_author, - avatar=item_author_avatar, - content=item_content, time=item_content_time, - original_id=item_comment_to_original_id) - - if item_type == "retweet": - # retweet - tweet = TweetItem(type=item_type, id=item_id, - author=item_author, - avatar=item_author_avatar, - content=item_content, time=item_content_time, - original_id=item_original_id, - original_content=item_original_content, - original_author=item_original_author, - original_time=item_original_time) - - if not item_thumb_pic is None: - # thumb pic - tweet.thumbnail_pic = item_thumb_pic - - model.appendRow(tweet) + for item in timeline: + model.appendRow(TweetItem(item)) self.timelineLoaded.emit(more) def get_all_timeline(self, page=1, reset_remind=False, more=False): From 9baba42a9888d58b6b2238e555f9b418575ac86f Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 11:29:37 +0800 Subject: [PATCH 12/38] =?UTF-8?q?=E5=85=81=E8=AE=B8=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E6=97=B6=E8=BE=93=E5=85=A5=E7=A9=BA=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wecase.py b/src/wecase.py index 5d78f04..8af7f7d 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -572,10 +572,10 @@ def __init__(self, parent=None, action="new", id=None, cid=None, text=""): self.textEdit.setText(text) self.textEdit.callback = self.mentions_suggest self.textEdit.mention_flag = "@" - self.checkChars() self.notify = Notify(timeout=1) def setupMyUi(self): + self.checkChars() if self.action == "new": self.chk_repost.setEnabled(False) self.chk_comment.setEnabled(False) @@ -705,8 +705,8 @@ def checkChars(self): text = self.textEdit.toPlainText() numLens = 140 - tweetLength(text) - if numLens == 140: - # you can not send empty tweet + if numLens == 140 and (not self.action == "retweet"): + # you can not send empty tweet, except retweet self.pushButton_send.setEnabled(False) elif numLens >= 0: # length is okay From 5000e7115456218bdb163af7c0d798fa1ca92079 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 12:46:26 +0800 Subject: [PATCH 13/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20@=E7=94=A8=E6=88=B7?= =?UTF-8?q?=20=E8=A1=A5=E5=85=A8=E6=97=A0=E6=B3=95=E8=A1=A5=E5=85=A8?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wecase.py b/src/wecase.py index 8af7f7d..cfdd8e5 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -591,7 +591,7 @@ def setupMyUi(self): def mentions_suggest(self, text): ret_users = [] try: - word = re.findall(r'@[-a-zA-Z0-9_\u4e00-\u9fa5]+', text)[-1].replace('@', '') + word = re.findall('@[-a-zA-Z0-9_\u4e00-\u9fa5]+', text)[-1].replace('@', '') except IndexError: return [] if not word.strip(): From 36d3c04d9fd0f246f1173cbf89d30b86e0d29345 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 15:47:21 +0800 Subject: [PATCH 14/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=8E=9F=E5=BE=AE=E5=8D=9A=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 3d84398..23cfb6e 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -72,6 +72,21 @@ Item { } } + function get_thumbnail_pic() { + if (thumbnail_pic) { + console.log(thumbnail_pic) + return thumbnail_pic + } + if (original && original.thumbnail_pic) { + console.log(original.thumbnail_pic) + return original.thumbnail_pic + } + else { + return "" + } + } + + Rectangle { id: avatarBackground width: 60 @@ -204,18 +219,18 @@ Item { Image { id: tweetImage - visible: thumbnail_pic + visible: get_thumbnail_pic() anchors.top: statusText.bottom - anchors.topMargin: thumbnail_pic ? 10 : 0; + anchors.topMargin: get_thumbnail_pic() ? 10 : 0; anchors.horizontalCenter: parent.horizontalCenter - source: thumbnail_pic + source: get_thumbnail_pic() MouseArea { anchors.fill: parent onClicked: { busy.on = true; - mainWindow.look_orignal_pic(thumbnail_pic, tweetid); + mainWindow.look_orignal_pic(get_thumbnail_pic(), tweetid); } } From b615f14f9eb786cbf0d9839c03f46bd00a920857 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 23:34:19 +0800 Subject: [PATCH 15/38] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E8=B0=83=E8=BD=AC=E5=88=B0=E5=8E=9F=E5=BE=AE?= =?UTF-8?q?=E5=8D=9A=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 11 ++++++++++- src/Tweet.py | 36 +++++++++++++++++++++++++++++------- src/TweetUtils.py | 28 ++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 23cfb6e..5465030 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -244,12 +244,21 @@ Item { Text { id: sinceText - text: tweetSinceTime + text: { + if (tweetType != 2) { + return "" + tweetSinceTime + "" + } + else { + return tweetSinceTime + } + } anchors.top: avatarBackground.bottom anchors.leftMargin: 11 anchors.topMargin: 5 anchors.left: parent.left font.family: "Segoe UI" font.pointSize: 7 + + onLinkActivated: container.handleLink(link); } } diff --git a/src/Tweet.py b/src/Tweet.py index c97dbc2..ce0fef0 100644 --- a/src/Tweet.py +++ b/src/Tweet.py @@ -6,6 +6,7 @@ from PyQt4 import QtCore from datetime import datetime +from TweetUtils import get_mid from WTimeParser import WTimeParser as time_parser @@ -58,12 +59,14 @@ class TweetItem(QtCore.QObject): roles = { QtCore.Qt.UserRole + 1: "type", QtCore.Qt.UserRole + 2: "id", - QtCore.Qt.UserRole + 3: "author", - QtCore.Qt.UserRole + 4: "time", - QtCore.Qt.UserRole + 5: "text", - QtCore.Qt.UserRole + 6: "original", - QtCore.Qt.UserRole + 7: "thumbnail_pic", - QtCore.Qt.UserRole + 8: "original_pic" + QtCore.Qt.UserRole + 3: "mid", + QtCore.Qt.UserRole + 4: "url", + QtCore.Qt.UserRole + 5: "author", + QtCore.Qt.UserRole + 6: "time", + QtCore.Qt.UserRole + 7: "text", + QtCore.Qt.UserRole + 8: "original", + QtCore.Qt.UserRole + 9: "thumbnail_pic", + QtCore.Qt.UserRole + 10: "original_pic" } def __init__(self, item={}, parent=None): @@ -76,12 +79,14 @@ def __init__(self, item={}, parent=None): self._roleData = { "type": self.type, "id": self.id, + "mid": self.mid, + "url": self.url, "author": self.author, "time": self.time, "text": self.text, "original": self.original, "thumbnail_pic": self.thumbnail_pic, - "original_pic": self.original_pic + "original_pic": self.original_pic, } def data(self, key): @@ -100,6 +105,23 @@ def type(self): def id(self): return self._data.get('idstr') + @QtCore.pyqtProperty(str, constant=True) + def mid(self): + decimal_mid = str(self._data.get('mid')) + encode_mid = get_mid(decimal_mid) + return encode_mid + + @QtCore.pyqtProperty(str, constant=True) + def url(self): + try: + uid = self._data['user']['id'] + mid = get_mid(self._data['mid']) + except KeyError: + # Sometimes Sina's API doesn't return user + # when our tweet is deeply nested. Just forgot it. + return "" + return 'http://weibo.com/%s/%s' % (uid, mid) + @QtCore.pyqtProperty(QtCore.QObject, constant=True) def author(self): if "user" in self._data: diff --git a/src/TweetUtils.py b/src/TweetUtils.py index 2561f48..db301ae 100644 --- a/src/TweetUtils.py +++ b/src/TweetUtils.py @@ -2,13 +2,13 @@ # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # WeCase -- This model implemented a bug-for-bug compatible -# strings' length counter with Sina's +# strings' length counter with Sina's # Copyright (C) 2013 Tom Li # License: GPL v3 or later. import re -from math import ceil +from math import ceil, floor def tweetLength(text): @@ -51,3 +51,27 @@ def findall(regex, text): (byteLen - TWEET_MAX + TWEET_URL_LEN)) n = n.replace(url, "") return ceil((total + len(n) + len(re.findall(r"[^\x00-\x80]", n))) / 2) + + +def get_mid(mid): + """Convert a id of a tweet to a mid.""" + + def baseN(num, base): + """Convert the base of a decimal.""" + CHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + return ((num == 0) and "0") or (baseN(num // base, base).lstrip("0") + + CHAR[num % base]) + + url = "" + + i = len(mid) - 7 + while i > -7: + offset_1 = 0 if i < 0 else i + offset_2 = i + 7 + num = mid[offset_1:offset_2] + + num = baseN(int(num), 62) + url = num + url + + i -= 7 + return url From 3031cf30826b2e46bfaba9d8474430404305165d Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 20 Apr 2013 23:35:56 +0800 Subject: [PATCH 16/38] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 5465030..87df6f4 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -74,11 +74,9 @@ Item { function get_thumbnail_pic() { if (thumbnail_pic) { - console.log(thumbnail_pic) return thumbnail_pic } if (original && original.thumbnail_pic) { - console.log(original.thumbnail_pic) return original.thumbnail_pic } else { From f6cdac017af0544713e3609a250f7ab48e1b13f8 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 21 Apr 2013 00:27:25 +0800 Subject: [PATCH 17/38] =?UTF-8?q?=E6=97=A0=E5=9B=BE=E7=89=87=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E6=97=B6=E4=B8=8D=E5=BA=94=E6=94=B9=E5=8F=98=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wecase.py b/src/wecase.py index cfdd8e5..f5bdeb3 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -683,7 +683,9 @@ def addImage(self): self.tr("Choose a" " image"), filter=ACCEPT_TYPE) - self.pushButton_picture.setText(self.tr("Remove the picture")) + # user may cancel the dialog, so check again + if self.image: + self.pushButton_picture.setText(self.tr("Remove the picture")) def showError(self, e): if "Text too long" in e: From 6d416b12e73ec73c48cb2ae999e96ddf85e818e5 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 21 Apr 2013 00:31:05 +0800 Subject: [PATCH 18/38] =?UTF-8?q?=E5=8F=91=E8=A1=A8=E9=9D=9E=E6=96=B0?= =?UTF-8?q?=E5=BE=AE=E5=8D=9A=E6=97=B6=EF=BC=8C=E4=B8=8D=E5=BA=94=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E5=8F=91=E9=80=81=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wecase.py b/src/wecase.py index f5bdeb3..ad5c6f8 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -582,11 +582,14 @@ def setupMyUi(self): self.chk_comment_original.setEnabled(False) elif self.action == "retweet": self.chk_repost.setEnabled(False) + self.pushButton_picture.setEnabled(False) elif self.action == "comment": self.chk_comment.setEnabled(False) + self.pushButton_picture.setEnabled(False) elif self.action == "reply": self.chk_repost.setEnabled(False) self.chk_comment.setEnabled(False) + self.pushButton_picture.setEnabled(False) def mentions_suggest(self, text): ret_users = [] From 2dd32ce4529520f02bd4d14d1646b10960dbeeee Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 21 Apr 2013 13:27:20 +0800 Subject: [PATCH 19/38] =?UTF-8?q?=E6=96=B0=E7=9B=AE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP b/ROADMAP index 0419ee4..82da794 100644 --- a/ROADMAP +++ b/ROADMAP @@ -1 +1,2 @@ 复制粘贴支持 +词语过滤支持,使用一个代理 Model 实现 From 5df99ecdf24b8869942962a8b888bd581bcf74a2 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 21 Apr 2013 21:55:44 +0800 Subject: [PATCH 20/38] =?UTF-8?q?=E5=A4=8D=E5=88=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=88=E5=AD=98=E5=9C=A8=E9=97=AE=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 6 ++++-- res/ui/TweetList.qml | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 87df6f4..42e5449 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -1,4 +1,4 @@ -import QtQuick 1.0 +import QtQuick 1.1 Item { id: container @@ -190,7 +190,7 @@ Item { onClicked: favoriteButtonClicked(); } - Text { + TextEdit { id: statusText color: "#333333" text: { @@ -211,6 +211,8 @@ Item { wrapMode: "Wrap" font.family: "Segoe UI" font.pointSize: 9 + selectByMouse: true + readOnly: true onLinkActivated: container.handleLink(link); } diff --git a/res/ui/TweetList.qml b/res/ui/TweetList.qml index 47c7331..3a946e3 100644 --- a/res/ui/TweetList.qml +++ b/res/ui/TweetList.qml @@ -30,6 +30,7 @@ Rectangle { id: tweetListView anchors.fill: parent; clip: true + cacheBuffer: 5000 // XXX: Free Memory Killer model: mymodel delegate: TweetDelegate { From ccdd1ec1a02b3df56dffd33426a8cb2b96e17557 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 22 Apr 2013 12:56:19 +0800 Subject: [PATCH 21/38] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E6=94=AF=E6=8C=81=EF=BC=8C=E6=96=B0=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ROADMAP | 3 ++- res/ui/TweetDelegate.qml | 10 +++++++--- res/ui/TweetList.qml | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ROADMAP b/ROADMAP index 82da794..1387fef 100644 --- a/ROADMAP +++ b/ROADMAP @@ -1,2 +1,3 @@ -复制粘贴支持 词语过滤支持,使用一个代理 Model 实现 +Context Menu 支持 +仅仅在双击后进入 TextEdit 状态,平时处于 Text,提高性能 diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 42e5449..c32104f 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -27,7 +27,11 @@ Item { signal mentionLinkClicked(string screenname) width: ListView.view.width; - height: { + height: getHeight(); + + /*Component.onCompleted: height = getHeight()*/ + + function getHeight() { var tweetImageHeight = tweetImage.paintedHeight if (statusText.paintedHeight < 80) { return 90 + tweetImageHeight; @@ -203,8 +207,8 @@ Item { + addTags(tweetOriginalText) } } - anchors.topMargin: 4 - anchors.top: parent.top; + y: 4; // anchors.topMargin: 4; anchors.top: parent.top; + // Binding loop detected for property "height", do not use it anchors.right: retweet.left; anchors.rightMargin: 0 anchors.left: avatarBackground.right; anchors.leftMargin: 10 textFormat: Text.RichText diff --git a/res/ui/TweetList.qml b/res/ui/TweetList.qml index 3a946e3..eb3961c 100644 --- a/res/ui/TweetList.qml +++ b/res/ui/TweetList.qml @@ -30,7 +30,7 @@ Rectangle { id: tweetListView anchors.fill: parent; clip: true - cacheBuffer: 5000 // XXX: Free Memory Killer + cacheBuffer: 500 model: mymodel delegate: TweetDelegate { From 90097b8ff6999494076913222a40c2aefd1b0c22 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 22 Apr 2013 21:35:44 +0800 Subject: [PATCH 22/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=AE=A1=E7=AE=97=20mi?= =?UTF-8?q?d=20=E6=97=B6=E6=9C=AA=E8=A1=A5=200=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E5=87=BA=E9=94=99=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/TweetUtils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/TweetUtils.py b/src/TweetUtils.py index db301ae..96ad268 100644 --- a/src/TweetUtils.py +++ b/src/TweetUtils.py @@ -8,7 +8,7 @@ import re -from math import ceil, floor +from math import ceil def tweetLength(text): @@ -69,8 +69,13 @@ def baseN(num, base): offset_1 = 0 if i < 0 else i offset_2 = i + 7 num = mid[offset_1:offset_2] - num = baseN(int(num), 62) + + if not len(num) == 1: + # if it isn't the first char of the mid, and it's length less than + # four chars, add zero at left for spacing + num = num.rjust(4, "0") + url = num + url i -= 7 From f61bedada67181ef2e886a367f91351420d75204 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 22 Apr 2013 21:36:16 +0800 Subject: [PATCH 23/38] =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E5=BE=AE=E5=8D=9A?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E6=97=B6=E5=BC=95=E7=94=A8=E5=8E=9F=E5=BE=AE?= =?UTF-8?q?=E5=8D=9A=EF=BC=8C=E9=98=B2=E6=AD=A2=E8=AF=BB=E8=80=85=E4=B8=8D?= =?UTF-8?q?=E7=9F=A5=E6=89=80=E4=BA=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index c32104f..820e412 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -45,6 +45,7 @@ Item { if (link.slice(0, 3) == 'tag') { hashtagLinkClicked(link.slice(6)) } else if (link.slice(0, 4) == 'http') { + console.log(link) Qt.openUrlExternally(link); } else if (link.slice(0, 7) == 'mention') { mentionLinkClicked(link.slice(10)); @@ -198,10 +199,10 @@ Item { id: statusText color: "#333333" text: { - if (tweetType == 0 || tweetType == 2) { + if (tweetType == 0) { return '' + tweetScreenName + ':<\/b>
' + addTags(tweetText) } - else if (tweetType == 1) { + else if (tweetType == 1 || tweetType == 2) { return '' + tweetScreenName + ':<\/b>
' + addTags(tweetText) + '
' + '    ' + tweetOriginalName + '<\/b>: ' + addTags(tweetOriginalText) @@ -252,6 +253,9 @@ Item { if (tweetType != 2) { return "" + tweetSinceTime + "" } + else if (tweetType == 2) { + return "" + tweetSinceTime + "" + } else { return tweetSinceTime } From c9ed67be369ef352731d07e513c5c6b4cc53f09e Mon Sep 17 00:00:00 2001 From: Tom Li Date: Mon, 22 Apr 2013 21:39:25 +0800 Subject: [PATCH 24/38] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/TweetDelegate.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index 820e412..eefd02c 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -45,7 +45,6 @@ Item { if (link.slice(0, 3) == 'tag') { hashtagLinkClicked(link.slice(6)) } else if (link.slice(0, 4) == 'http') { - console.log(link) Qt.openUrlExternally(link); } else if (link.slice(0, 7) == 'mention') { mentionLinkClicked(link.slice(10)); From 2120b3c59e930e5e9a9f611310d35a804e8cbd7f Mon Sep 17 00:00:00 2001 From: Tom Li Date: Tue, 23 Apr 2013 12:58:59 +0800 Subject: [PATCH 25/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E9=94=99=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/img/smiley/L6/clock_thumb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/img/smiley/L6/clock_thumb b/res/img/smiley/L6/clock_thumb index 9c5dd46..92f0862 100644 --- a/res/img/smiley/L6/clock_thumb +++ b/res/img/smiley/L6/clock_thumb @@ -1 +1 @@ -种 +钟 From 65dea0946d7cbb6243bbfc0ffe6c5717c9151286 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 15:05:11 +0800 Subject: [PATCH 26/38] =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=BA=BF=E5=91=A8?= =?UTF-8?q?=E5=9B=B4=E5=8A=A0=E4=B8=80=E5=9C=88=E5=85=89=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- res/ui/MainWindow.ui | 36 ++++++++++++++++++++++++++++++++---- res/ui/TweetList.qml | 2 +- src/MainWindow_ui.py | 14 +++++++++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/res/ui/MainWindow.ui b/res/ui/MainWindow.ui index dbbcb0b..e595a9b 100644 --- a/res/ui/MainWindow.ui +++ b/res/ui/MainWindow.ui @@ -30,7 +30,14 @@ - + + + QFrame::StyledPanel + + + QFrame::Sunken + + @@ -40,7 +47,14 @@ - + + + QFrame::StyledPanel + + + QFrame::Sunken + + @@ -50,7 +64,14 @@ - + + + QFrame::StyledPanel + + + QFrame::Sunken + + @@ -60,7 +81,14 @@ - + + + QFrame::StyledPanel + + + QFrame::Sunken + + diff --git a/res/ui/TweetList.qml b/res/ui/TweetList.qml index eb3961c..28f49e0 100644 --- a/res/ui/TweetList.qml +++ b/res/ui/TweetList.qml @@ -94,7 +94,7 @@ Rectangle { id: scrollbar anchors.right: tweetListView.right y: tweetListView.visibleArea.yPosition * tweetListView.height - width: 2 + width: 8 height: tweetListView.visibleArea.heightRatio * tweetListView.height color: "black" } diff --git a/src/MainWindow_ui.py b/src/MainWindow_ui.py index d64ebaa..4a595b8 100644 --- a/src/MainWindow_ui.py +++ b/src/MainWindow_ui.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -# Form implementation generated from reading ui file './ui/MainWindow.ui' +# Form implementation generated from reading ui file 'ui/MainWindow.ui' # -# Created: Sun Apr 14 21:55:20 2013 -# by: PyQt4 UI code generator 4.10 +# Created: Sat Apr 27 15:00:44 2013 +# by: PyQt4 UI code generator 4.10.1 # # WARNING! All changes made in this file will be lost! @@ -41,6 +41,8 @@ def setupUi(self, frm_MainWindow): self.verticalLayout_2 = QtGui.QVBoxLayout(self.tab) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.homeView = QtDeclarative.QDeclarativeView(self.tab) + self.homeView.setFrameShape(QtGui.QFrame.StyledPanel) + self.homeView.setFrameShadow(QtGui.QFrame.Sunken) self.homeView.setObjectName(_fromUtf8("homeView")) self.verticalLayout_2.addWidget(self.homeView) self.tabWidget.addTab(self.tab, _fromUtf8("")) @@ -49,6 +51,8 @@ def setupUi(self, frm_MainWindow): self.verticalLayout_3 = QtGui.QVBoxLayout(self.tab_2) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.mentionsView = QtDeclarative.QDeclarativeView(self.tab_2) + self.mentionsView.setFrameShape(QtGui.QFrame.StyledPanel) + self.mentionsView.setFrameShadow(QtGui.QFrame.Sunken) self.mentionsView.setObjectName(_fromUtf8("mentionsView")) self.verticalLayout_3.addWidget(self.mentionsView) self.tabWidget.addTab(self.tab_2, _fromUtf8("")) @@ -57,6 +61,8 @@ def setupUi(self, frm_MainWindow): self.verticalLayout_4 = QtGui.QVBoxLayout(self.tab_3) self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) self.commentsView = QtDeclarative.QDeclarativeView(self.tab_3) + self.commentsView.setFrameShape(QtGui.QFrame.StyledPanel) + self.commentsView.setFrameShadow(QtGui.QFrame.Sunken) self.commentsView.setObjectName(_fromUtf8("commentsView")) self.verticalLayout_4.addWidget(self.commentsView) self.tabWidget.addTab(self.tab_3, _fromUtf8("")) @@ -65,6 +71,8 @@ def setupUi(self, frm_MainWindow): self.verticalLayout_5 = QtGui.QVBoxLayout(self.tab_4) self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) self.myView = QtDeclarative.QDeclarativeView(self.tab_4) + self.myView.setFrameShape(QtGui.QFrame.StyledPanel) + self.myView.setFrameShadow(QtGui.QFrame.Sunken) self.myView.setObjectName(_fromUtf8("myView")) self.verticalLayout_5.addWidget(self.myView) self.tabWidget.addTab(self.tab_4, _fromUtf8("")) From 3bfaf6e3c1252b4463c22d088d5046d91ce6489b Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 15:14:14 +0800 Subject: [PATCH 27/38] =?UTF-8?q?=E5=9B=A0=E7=BD=91=E7=BB=9C=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=97=B6=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/WeCase_zh_CN.qm | Bin 5383 -> 5645 bytes locale/WeCase_zh_CN.ts | 97 ++++++++++++++++++++++------------------- src/wecase.py | 15 ++++--- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/locale/WeCase_zh_CN.qm b/locale/WeCase_zh_CN.qm index 6b2c11e3caccb136cb22a6d3d731aa6fe47c0d35..f403e23f4b0b7aaa6ff93cd7196e78a3ab9b1f12 100644 GIT binary patch delta 910 zcmX|8ZAepL6h3#m_wIge)ut7Nbs^21(x+ODC}KrAA}cM*RXXQTY2It@W?JM3iV8EU zkxm;0sbodVSoK5AicCnNunburLRk@FWmE=It}_SzczND)-gBPwJm)=btWG);BdD(g*fJ-dYXFE9u=_ruTiX@67?u-;_c;LkZNl*7 zDu6bZNIvl!V9F|@qIVMxzM$gXvG`uDx;^MdfeEU+Jc@;JReMDM-wV|{6$J(Ks4wn0 z20&M-heJnDU@j@dpiss{Zt1Zg-!Jl{wGaE^$qyYqa06|j)Q2AdXzHkijmcO*QYp3L zhzhm*dIAm#QQL0@0XQ#ps;mi#JE^vY0~l|j`c(B84^csH*cn8S$crG`5J7Ey1PvT5 z&RdH@TIslJi0pjY{Lp|-I5B)d$IBw9Z=nw{gZL#*daUCW3VTG4b)o}mm*$3M09RM7 z89KNRiOQHA>@9q^GJ)0?IR7bg@ALxfYh*Rs{FonMg_R!MOe~wjXJCE_>$Ku`PK{;j zf@p|e%56?`>+o+sa`~&G&`AvEP50rTc<##QUd(H`mJSnc;tA(pJ%;Gxvn@X`pT-{= zI0~Te=et{Q)p|W2^xi>C6S$^096VQO`|iSluZ4C3ktc;`doJN73xuC9k%#NjHh(&c z28Ol&W^-$>;Hz%MUrY#%Q_hZ}GVQk_0E6ALnvV%wQ7s~O&nbLR@F};Y>}d7;%s(N) z+Fed$opNufd|NYMZw?Vmc>6S-6my}&zE`Y}_m_$`yItNdDM@09&E+~Em*$H$DPK&N z6i2D#P(-sVNe;VGBukzL^z?vHlS)Kq369awtVuR%t!4R$}Msh_KGfImJiX@wJqCJOM7ERVTGkYgX>8*|u*)_ox I@Gy$-AJbmw#QFI(p z@IsclctD7}4@&C=>+?{|Nz$op*J*wsR7rD* zf=>&RJs%)2B1}GkV?J4YCBfB|X}NC+t%^TfLYsDR;jP;cQfzdt6CKKB}~& zOoE+iRiKv0JfZeCW0mF@)$i#5b2a5wBn%{KT|XTV_^ov_pc0`CW?#pX)M&rnqhAha z(^GZ~-eSBx)`;YSaavi2x@bz6!ddMNYWxdBMyY@(q}qTeoZHOJ_q}FUamiu%&kl|4 zZ$4IAw{+LP%=5PXa%Ps1=JqY%vsm*F<{h?F6gf*Q_Po3zXJM(uUUYYi7|*zq @default - + WeCase 微盒 @@ -94,7 +94,7 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. Check your account and password! - 检查您的账号和密码! + 检查您的账号和密码! @@ -106,6 +106,11 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. Login, waiting... 登录中,请稍候…… + + + Check your account, password and Internet Connection! + 检查您的账号,密码和网络连接! +
NewPostWindow @@ -147,48 +152,48 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. Also: - + 同时: Comment - + 评论 Repost - + 转发 Commmet to Original - + 评论给原微博 NewpostWindow - + WeCase 微盒 - + Retweet Success! 转发成功! - + Comment Success! 评论成功! - + Reply Success! 回复成功! - + Tweet Success! 发送成功! @@ -198,37 +203,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 图片 (*.png *.jpg *.bmp *.gif) - + Picture 图片 - + Choose a image 选择一张图片 - + Remove the picture 移除图片 - + Text too long! 内容过长! - + Please remove some text. 请删除一些文字。 - + Unknown error! 未知错误! - + Images 图片 @@ -287,27 +292,27 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. TweetItem - + Time travel! 穿越时空! - + %.0f seconds ago %.0f 秒前 - + %.0f minutes ago %.0f 分钟前 - + %.0f hours ago %.0f 小时前 - + %.0f days ago %.0f 天前 @@ -315,37 +320,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. WeCaseWindow - + Weibo(%d) 微博(%d) - + @Me(%d) @我(%d) - + Comments(%d) 评论(%d) - + WeCase 微盒 - + Weibo 微博 - + @ME @我 - + Comments 评论 @@ -353,12 +358,12 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. WeSettingsWindow - + %i min %i sec %i 分 %i 秒 - + %i sec %i 秒 @@ -419,72 +424,72 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 微博 - + @ME @我 - + Comments 评论 - + My tweet 我的微博 - + &Me 我(&M) - + &Refresh 刷新(&R) - + &New Weibo 新微博(&N) - + &WeCase 微盒(&W) - + &Help 帮助(&H) - + &Options 选项(&H) - + &About... 关于(&A)... - + &Log out 注销(&L) - + &Exit 退出(&E) - + &Settings 设置(&S) - + &Update 升级(&U) diff --git a/src/wecase.py b/src/wecase.py index ad5c6f8..4e876da 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -88,8 +88,8 @@ def accept(self, client): def reject(self): QtGui.QMessageBox.critical(None, self.tr("Authorize Failed!"), - self.tr("Check your account and " - "password!")) + self.tr("Check your account, " + "password and Internet Connection!")) self.pushButton_log.setText(self.tr("GO!")) self.pushButton_log.setEnabled(True) @@ -165,9 +165,14 @@ def authorize(self, username, password): postdata = urllib.parse.urlencode(oauth2) conn = http.client.HTTPSConnection('api.weibo.com') - conn.request('POST', '/oauth2/authorize', postdata, - {'Referer': authorize_url, - 'Content-Type': 'application/x-www-form-urlencoded'}) + try: + conn.request('POST', '/oauth2/authorize', postdata, + {'Referer': authorize_url, + 'Content-Type': 'application/x-www-form-urlencoded'}) + except OSError: + self.loginReturn.emit(None) + return + res = conn.getresponse() From c4c791e795ab8538f58d85f85b03e226e19d8711 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 15:20:53 +0800 Subject: [PATCH 28/38] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/WeCase_zh_CN.qm | Bin 5645 -> 5854 bytes locale/WeCase_zh_CN.ts | 15 +++++++++++++++ src/wecase.py | 8 ++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/locale/WeCase_zh_CN.qm b/locale/WeCase_zh_CN.qm index f403e23f4b0b7aaa6ff93cd7196e78a3ab9b1f12..554d2006ebd59692747210227a0a8af23d3f88cd 100644 GIT binary patch delta 549 zcmeCxxu-ipByt4>tTG*U{!AFpRfn_%X(>fr{ z-^;)v?##fzd5nQY6{wH%3xikL}wS%Bt9Gibdr0Mh;wQ}wj?d>LZ*_cJgE ze_#}T7|+1Ky@gTu#Y&**FBwfbz5-4A%2;{P4@ieh+$(3qugJ8&UlJ%Q$sAcy0yLwU zd0t#A0|Q$(^PB&TK)VuHcw~Wga#*nhp7LT~5Li2zl~G=BFRSUae?XottNCh8pze;z zri^x6d`hf0XG{fJeRy&$qrB!GHs^!FKwctSHOFHH1|e0pUwf`HFt8tH`?U{fBx?+N z-kVnp418-R?`2d|5{ltu4_yb8{=ms&ITw;SdS46_pFBC2 z$&8(^Jd=SzL}oG%i|pp(O#66rL>N>VQWzA{6VtW2e={gBI3!<~IEjM+2-tv_S2abU zG_NQ%F-5__*LAX?fS7?0vWD&>-MbhR;L1f1%9HbRb5rw5G>Ru12uMuU6&2k)o8O#K giY;M9I_G~WxDL+9{8ELC#IjT?kl~vz30SiN0Jh7CD*ylh delta 427 zcmXAlODIHP9L3Lf=eu{@$1wK}Sxqtx@{GLl$VN)?$if2S5gUq9W2uL0ElNmRRW;h z{i`tw)an2~gy18My#Y_G1mtt@6uOf`$hhKwy$cy1xui=A{W)W{Q?wn;0ghtkc@gNX-;;mL(AEo&tv z)pE-nY6E@Gllxj}AiLXi3OS{57mBYUOp$A9OU(~$R;`XaO%VM~J>R1ChN0ds?~zt( q%4Q1TlC*=bLBc$klR}Zz_Ss9lx=oMf^gjzh&7W$oWBv`jO#A~=V{3u{ diff --git a/locale/WeCase_zh_CN.ts b/locale/WeCase_zh_CN.ts index bbd04d3..2e80cd4 100644 --- a/locale/WeCase_zh_CN.ts +++ b/locale/WeCase_zh_CN.ts @@ -354,6 +354,21 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. Comments 评论 + + + You have: + 您有: + + + + %d unread @ME + %d 条未读 @提醒 + + + + %d unread comment(s) + %d 条未读评论 + WeSettingsWindow diff --git a/src/wecase.py b/src/wecase.py index 4e876da..55b1056 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -362,7 +362,7 @@ def show_notify(self): # to display unread count reminds = self.get_remind(self.uid) - msg = "You have:\n" + msg = self.tr("You have:") + "\n" num_msg = 0 if reminds['status'] != 0: @@ -371,18 +371,18 @@ def show_notify(self): % reminds['status']) if reminds['mention_status'] and self.remindMentions: - msg += "%d unread @ME\n" % reminds['mention_status'] + msg += self.tr("%d unread @ME") + "\n" % reminds['mention_status'] self.tabTextChanged.emit(1, self.tr("@Me(%d)") % reminds['mention_status']) num_msg += 1 if reminds['cmt'] and self.remindComments: - msg += "%d unread comment(s)\n" % reminds['cmt'] + msg += self.tr("%d unread comment(s)") + "\n" % reminds['cmt'] self.tabTextChanged.emit(2, self.tr("Comments(%d)") % reminds['cmt']) num_msg += 1 - if num_msg != 0: + if num_msg: self.notify.showMessage(self.tr("WeCase"), msg) def setTabText(self, index, string): From d31d23b65ed19938c67cbca26675163dc4db77e3 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 15:23:15 +0800 Subject: [PATCH 29/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E6=A0=BC=E5=BC=8F=E5=8C=96=E7=AC=94=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wecase.py b/src/wecase.py index 55b1056..29ee90d 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -371,13 +371,13 @@ def show_notify(self): % reminds['status']) if reminds['mention_status'] and self.remindMentions: - msg += self.tr("%d unread @ME") + "\n" % reminds['mention_status'] + msg += self.tr("%d unread @ME" % reminds['mention_status']) + "\n" self.tabTextChanged.emit(1, self.tr("@Me(%d)") % reminds['mention_status']) num_msg += 1 if reminds['cmt'] and self.remindComments: - msg += self.tr("%d unread comment(s)") + "\n" % reminds['cmt'] + msg += self.tr("%d unread comment(s)" % reminds['cmt']) + "\n" self.tabTextChanged.emit(2, self.tr("Comments(%d)") % reminds['cmt']) num_msg += 1 From 9114ed7c4c699a227f3ceb60c293d8e9eb68fb0c Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 15:27:16 +0800 Subject: [PATCH 30/38] =?UTF-8?q?=E5=86=8D=E6=AC=A1=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/wecase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wecase.py b/src/wecase.py index 29ee90d..d98c32e 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -371,13 +371,13 @@ def show_notify(self): % reminds['status']) if reminds['mention_status'] and self.remindMentions: - msg += self.tr("%d unread @ME" % reminds['mention_status']) + "\n" + msg += self.tr("%d unread @ME") % reminds['mention_status'] + "\n" self.tabTextChanged.emit(1, self.tr("@Me(%d)") % reminds['mention_status']) num_msg += 1 if reminds['cmt'] and self.remindComments: - msg += self.tr("%d unread comment(s)" % reminds['cmt']) + "\n" + msg += self.tr("%d unread comment(s)") % reminds['cmt'] + "\n" self.tabTextChanged.emit(2, self.tr("Comments(%d)") % reminds['cmt']) num_msg += 1 From 2b092fa8a64870f86b80f4fe76e96f918da0f6bd Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 16:15:36 +0800 Subject: [PATCH 31/38] Split files, fixed Issue #35. --- src/AboutWindow.py | 16 + src/LoginWindow.py | 178 ++++++++++ src/NewpostWindow.py | 185 ++++++++++ src/Notify.py | 25 ++ src/SettingWindow.py | 71 ++++ src/Smiley.py | 3 +- src/SmileyWindow.py | 39 +++ src/WTimer.py | 1 - src/WeCaseWindow.py | 315 +++++++++++++++++ src/const.py | 31 ++ src/wecase.py | 783 +------------------------------------------ 11 files changed, 874 insertions(+), 773 deletions(-) create mode 100644 src/AboutWindow.py create mode 100644 src/LoginWindow.py create mode 100644 src/NewpostWindow.py create mode 100644 src/Notify.py create mode 100644 src/SettingWindow.py create mode 100644 src/SmileyWindow.py create mode 100644 src/WeCaseWindow.py create mode 100644 src/const.py diff --git a/src/AboutWindow.py b/src/AboutWindow.py new file mode 100644 index 0000000..fda94db --- /dev/null +++ b/src/AboutWindow.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented AboutWindow. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +from PyQt4 import QtGui +from AboutWindow_ui import Ui_About_Dialog + + +class AboutWindow(QtGui.QDialog, Ui_About_Dialog): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) diff --git a/src/LoginWindow.py b/src/LoginWindow.py new file mode 100644 index 0000000..4fa7314 --- /dev/null +++ b/src/LoginWindow.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented LoginWindow. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +import webbrowser +import urllib.request +import urllib.parse +import urllib.error +import http.client +from configparser import ConfigParser +import threading +from weibo import APIClient +from PyQt4 import QtCore, QtGui +from LoginWindow_ui import Ui_frm_Login +from WeCaseWindow import WeCaseWindow +import const + + +class LoginWindow(QtGui.QDialog, Ui_frm_Login): + loginReturn = QtCore.pyqtSignal(object) + + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.loadConfig() + self.setupUi(self) + self.setupMyUi() + self.setupSignals() + + def setupSignals(self): + # Other singals defined in Desinger. + self.loginReturn.connect(self.checkLogin) + self.chk_Remember.clicked.connect(self.uncheckAutoLogin) + + def accept(self, client): + if self.chk_Remember.isChecked(): + self.passwd[str(self.username)] = str(self.password) + self.last_login = str(self.username) + # Because this is a model dislog, + # closeEvent won't emit when we accept() the window, but will + # emit when we reject() the window. + self.saveConfig() + wecase_main = WeCaseWindow() + wecase_main.init_account(client) + wecase_main.show() + # Maybe users will logout, so reset the status + self.pushButton_log.setText(self.tr("GO!")) + self.pushButton_log.setEnabled(True) + self.done(True) + + def reject(self): + QtGui.QMessageBox.critical(None, self.tr("Authorize Failed!"), + self.tr("Check your account, " + "password and Internet Connection!") + ) + self.pushButton_log.setText(self.tr("GO!")) + self.pushButton_log.setEnabled(True) + + def checkLogin(self, client): + if client: + self.accept(client) + else: + self.reject() + + def setupMyUi(self): + self.show() + self.txt_Password.setEchoMode(QtGui.QLineEdit.Password) + self.cmb_Users.addItem(self.last_login) + + for username in list(self.passwd.keys()): + if username == self.last_login: + continue + self.cmb_Users.addItem(username) + + if self.cmb_Users.currentText(): + self.chk_Remember.setChecked(True) + self.setPassword(self.cmb_Users.currentText()) + + if self.auto_login: + self.chk_AutoLogin.setChecked(self.auto_login) + self.login() + + def loadConfig(self): + self.config = ConfigParser() + self.config.read(const.config_path) + + if not self.config.has_section('login'): + self.config['login'] = {} + + self.login_config = self.config['login'] + self.passwd = eval(self.login_config.get('passwd', "{}")) + self.last_login = str(self.login_config.get('last_login', "")) + self.auto_login = self.login_config.getboolean('auto_login', 0) + + def saveConfig(self): + self.login_config['passwd'] = str(self.passwd) + self.login_config['last_login'] = self.last_login + self.login_config['auto_login'] = str(self.chk_AutoLogin.isChecked()) + + with open(const.config_path, "w+") as config_file: + self.config.write(config_file) + + def login(self): + self.pushButton_log.setText(self.tr("Login, waiting...")) + self.pushButton_log.setEnabled(False) + self.ui_authorize() + + def ui_authorize(self): + self.username = self.cmb_Users.currentText() + self.password = self.txt_Password.text() + threading.Thread(group=None, target=self.authorize, + args=(self.username, self.password)).start() + + def authorize(self, username, password): + # TODO: This method is very messy, maybe do some cleanup? + + client = APIClient(app_key=const.APP_KEY, app_secret=const.APP_SECRET, + redirect_uri=const.CALLBACK_URL) + + # Step 1: Get the authorize url from Sina + authorize_url = client.get_authorize_url() + + # Step 2: Send the authorize info to Sina and get the authorize_code + # TODO: Rewrite them with urllib/urllib2 + oauth2 = const.OAUTH2_PARAMETER + oauth2['userId'] = username + oauth2['passwd'] = password + postdata = urllib.parse.urlencode(oauth2) + + conn = http.client.HTTPSConnection('api.weibo.com') + try: + conn.request('POST', '/oauth2/authorize', postdata, + {'Referer': authorize_url, + 'Content-Type': 'application/x-www-form-urlencoded'}) + except OSError: + self.loginReturn.emit(None) + return + + res = conn.getresponse() + + location = res.getheader('location') + + if not location: + return self.loginReturn.emit(None) + + authorize_code = location.split('=')[1] + conn.close() + + # Step 3: Put the authorize information into SDK + r = client.request_access_token(authorize_code) + access_token = r.access_token + expires_in = r.expires_in + + client.set_access_token(access_token, expires_in) + self.loginReturn.emit(client) + + def setPassword(self, username): + if username: + self.txt_Password.setText(self.passwd[str(username)]) + + @QtCore.pyqtSlot(bool) + def uncheckAutoLogin(self, checked): + if not checked: + self.chk_AutoLogin.setChecked(False) + + def openRegisterPage(self): + webbrowser.open("http://weibo.com/signup/signup.php") + + def closeEvent(self, event): + # HACK: When a user want to close this window, closeEvent will emit. + # But if we don't have closeEvent, Qt will call reject(). We use + # reject() to show the error message, so users will see the error and + # they can not close this window. So just do nothing there to allow + # users to close the window. + pass diff --git a/src/NewpostWindow.py b/src/NewpostWindow.py new file mode 100644 index 0000000..d886a0e --- /dev/null +++ b/src/NewpostWindow.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented NewpostWindow. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +import re +import threading +from PyQt4 import QtCore, QtGui +from weibo import APIError +from Notify import Notify +from TweetUtils import tweetLength +from NewpostWindow_ui import Ui_NewPostWindow +from SmileyWindow import SmileyWindow + + +class NewpostWindow(QtGui.QDialog, Ui_NewPostWindow): + client = None + image = None + apiError = QtCore.pyqtSignal(str) + sendSuccessful = QtCore.pyqtSignal() + + def __init__(self, parent=None, action="new", id=None, cid=None, text=""): + QtGui.QDialog.__init__(self, parent) + self.action = action + self.id = id + self.cid = cid + self.setupUi(self) + self.setupMyUi() + self.textEdit.setText(text) + self.textEdit.callback = self.mentions_suggest + self.textEdit.mention_flag = "@" + self.notify = Notify(timeout=1) + + def setupMyUi(self): + self.checkChars() + if self.action == "new": + self.chk_repost.setEnabled(False) + self.chk_comment.setEnabled(False) + self.chk_comment_original.setEnabled(False) + elif self.action == "retweet": + self.chk_repost.setEnabled(False) + self.pushButton_picture.setEnabled(False) + elif self.action == "comment": + self.chk_comment.setEnabled(False) + self.pushButton_picture.setEnabled(False) + elif self.action == "reply": + self.chk_repost.setEnabled(False) + self.chk_comment.setEnabled(False) + self.pushButton_picture.setEnabled(False) + + def mentions_suggest(self, text): + ret_users = [] + try: + word = re.findall('@[-a-zA-Z0-9_\u4e00-\u9fa5]+', text)[-1] + word = word.replace('@', '') + except IndexError: + return [] + if not word.strip(): + return [] + users = self.client.search.suggestions.at_users.get(q=word, type=0) + for user in users: + ret_users.append("@" + user['nickname']) + return ret_users + + def send(self): + self.pushButton_send.setEnabled(False) + if self.action == "new": + threading.Thread(group=None, target=self.new).start() + elif self.action == "retweet": + threading.Thread(group=None, target=self.retweet).start() + elif self.action == "comment": + threading.Thread(group=None, target=self.comment).start() + elif self.action == "reply": + threading.Thread(group=None, target=self.reply).start() + + def retweet(self): + text = str(self.textEdit.toPlainText()) + try: + self.client.statuses.repost.post(id=int(self.id), status=text, + is_comment=int((self.chk_comment.isChecked() + + self.chk_comment_original.isChecked() * 2))) + self.notify.showMessage(self.tr("WeCase"), + self.tr("Retweet Success!")) + self.sendSuccessful.emit() + except APIError as e: + self.apiError.emit(str(e)) + return + + def comment(self): + text = str(self.textEdit.toPlainText()) + try: + self.client.comments.create.post(id=int(self.id), comment=text, + comment_ori=int(self.chk_comment_original.isChecked())) + if self.chk_repost.isChecked(): + self.client.statuses.repost.post(id=int(self.id), status=text) + self.notify.showMessage(self.tr("WeCase"), + self.tr("Comment Success!")) + self.sendSuccessful.emit() + except APIError as e: + self.apiError.emit(str(e)) + return + + def reply(self): + text = str(self.textEdit.toPlainText()) + try: + self.client.comments.reply.post(id=int(self.id), cid=int(self.cid), + comment=text, + comment_ori=int(self.chk_comment_original.isChecked())) + if self.chk_repost.isChecked(): + self.client.statuses.repost.post(id=int(self.id), status=text) + self.notify.showMessage(self.tr("WeCase"), + self.tr("Reply Success!")) + self.sendSuccessful.emit() + except APIError as e: + self.apiError.emit(str(e)) + return + + def new(self): + text = str(self.textEdit.toPlainText()) + + try: + if self.image: + self.client.statuses.upload.post(status=text, + pic=open(self.image, "rb")) + else: + self.client.statuses.update.post(status=text) + + self.notify.showMessage(self.tr("WeCase"), + self.tr("Tweet Success!")) + self.sendSuccessful.emit() + except APIError as e: + self.apiError.emit(str(e)) + return + + self.image = None + + def addImage(self): + ACCEPT_TYPE = self.tr("Images") + "(*.png *.jpg *.bmp *.gif)" + if self.image: + self.image = None + self.pushButton_picture.setText(self.tr("Picture")) + else: + self.image = QtGui.QFileDialog.getOpenFileName(self, + self.tr("Choose a" + " image"), + filter=ACCEPT_TYPE) + # user may cancel the dialog, so check again + if self.image: + self.pushButton_picture.setText(self.tr("Remove the picture")) + + def showError(self, e): + if "Text too long" in e: + QtGui.QMessageBox.warning(None, self.tr("Text too long!"), + self.tr("Please remove some text.")) + else: + QtGui.QMessageBox.warning(None, self.tr("Unknown error!"), e) + self.pushButton_send.setEnabled(True) + + def showSmiley(self): + wecase_smiley = SmileyWindow() + if wecase_smiley.exec_(): + self.textEdit.textCursor().insertText(wecase_smiley.smileyName) + + def checkChars(self): + '''Check textEdit's characters. + If it larger than 140, Send Button will be disabled + and label will show red chars.''' + + text = self.textEdit.toPlainText() + numLens = 140 - tweetLength(text) + if numLens == 140 and (not self.action == "retweet"): + # you can not send empty tweet, except retweet + self.pushButton_send.setEnabled(False) + elif numLens >= 0: + # length is okay + self.label.setStyleSheet("color:black;") + self.pushButton_send.setEnabled(True) + else: + # text is too long + self.label.setStyleSheet("color:red;") + self.pushButton_send.setEnabled(False) + self.label.setText(str(numLens)) diff --git a/src/Notify.py b/src/Notify.py new file mode 100644 index 0000000..dc552ab --- /dev/null +++ b/src/Notify.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented Notify. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +from PyQt4 import QtCore +import notify2 as pynotify +import const + + +class Notify(): + image = const.myself_path + "/ui/img/WeCase 80.png" + + def __init__(self, appname=QtCore.QObject().tr("WeCase"), timeout=5): + pynotify.init(appname) + self.timeout = timeout + self.n = pynotify.Notification(appname) + + def showMessage(self, title, text): + self.n.update(title, text, self.image) + self.n.set_timeout(self.timeout * 1000) + self.n.show() diff --git a/src/SettingWindow.py b/src/SettingWindow.py new file mode 100644 index 0000000..0fb8f11 --- /dev/null +++ b/src/SettingWindow.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented SettingWindow. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +from PyQt4 import QtGui +from configparser import ConfigParser +from SettingWindow_ui import Ui_SettingWindow +import const + + +class WeSettingsWindow(QtGui.QDialog, Ui_SettingWindow): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + self.loadConfig() + + def transformInterval(self, sliderValue): + return (sliderValue // 60, sliderValue % 60) + + def setIntervalText(self, sliderValue): + self.intervalLabel.setText(self.tr("%i min %i sec") % + (self.transformInterval(sliderValue))) + + def setTimeoutText(self, sliderValue): + self.timeoutLabel.setText(self.tr("%i sec") % sliderValue) + + def loadConfig(self): + self.config = ConfigParser() + self.config.read(const.config_path) + + if not self.config.has_section('main'): + self.config['main'] = {} + + self.main_config = self.config['main'] + self.intervalSlider.setValue(int(self.main_config.get( + 'notify_interval', "30"))) + self.setIntervalText(self.intervalSlider.value()) + self.timeoutSlider.setValue(int(self.main_config.get( + "notify_timeout", "5"))) + self.setTimeoutText(self.timeoutSlider.value()) + self.commentsChk.setChecked(self.main_config.getboolean( + "remind_comments", True)) + self.mentionsChk.setChecked(self.main_config.getboolean( + "remind_mentions", True)) + + def saveConfig(self): + self.config = ConfigParser() + self.config.read(const.config_path) + + if not self.config.has_section('main'): + self.config['main'] = {} + + self.main_config = self.config['main'] + self.main_config['notify_interval'] = str(self.intervalSlider.value()) + self.main_config['notify_timeout'] = str(self.timeoutSlider.value()) + self.main_config['remind_comments'] = str(self.commentsChk.isChecked()) + self.main_config['remind_mentions'] = str(self.mentionsChk.isChecked()) + + with open(const.config_path, "w+") as config_file: + self.config.write(config_file) + + def accept(self): + self.saveConfig() + self.done(True) + + def reject(self): + self.done(False) diff --git a/src/Smiley.py b/src/Smiley.py index a9b1d51..5ede37d 100644 --- a/src/Smiley.py +++ b/src/Smiley.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 -# WeCase -- This model implemented a model for smileies +# WeCase -- This model implemented a model for smileies. # Copyright: GPL v3 or later. + import sys import os from PyQt4 import QtCore diff --git a/src/SmileyWindow.py b/src/SmileyWindow.py new file mode 100644 index 0000000..4218400 --- /dev/null +++ b/src/SmileyWindow.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented SmileyWindow. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +from PyQt4 import QtCore, QtGui +from Smiley import SmileyModel, SmileyItem +from SmileyWindow_ui import Ui_SmileyWindow +import const + + +class SmileyWindow(QtGui.QDialog, Ui_SmileyWindow): + def __init__(self, parent=None): + QtGui.QDialog.__init__(self, parent) + self.setupUi(self) + self.setupMyUi() + self.setupModels() + self.smileyName = "" + + def setupMyUi(self): + self.smileyView.setResizeMode(self.smileyView.SizeRootObjectToView) + + def setupModels(self): + self.smileyModel = SmileyModel(self) + self.smileyModel.init_smileies(const.myself_path + "./ui/img/smiley", + self.smileyModel, SmileyItem) + self.smileyView.rootContext().setContextProperty("SmileyModel", + self.smileyModel) + self.smileyView.rootContext().setContextProperty("parentWindow", self) + self.smileyView.setSource(QtCore.QUrl.fromLocalFile( + const.myself_path + "/ui/SmileyView.qml")) + + @QtCore.pyqtSlot(str) + def returnSmileyName(self, smileyName): + self.smileyName = smileyName + self.done(True) diff --git a/src/WTimer.py b/src/WTimer.py index 67b5264..cb03959 100644 --- a/src/WTimer.py +++ b/src/WTimer.py @@ -7,7 +7,6 @@ # License: GPL v3 or later. -import time import threading diff --git a/src/WeCaseWindow.py b/src/WeCaseWindow.py new file mode 100644 index 0000000..7c32d0b --- /dev/null +++ b/src/WeCaseWindow.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file implemented WeCaseWindow, the mainWindow of WeCase. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +import os +import urllib.request +import urllib.parse +import urllib.error +from configparser import ConfigParser +import threading +from WTimer import WTimer +from PyQt4 import QtCore, QtGui +from Tweet import TweetModel, TweetItem +from MainWindow_ui import Ui_frm_MainWindow +from Notify import Notify +from NewpostWindow import NewpostWindow +from SettingWindow import WeSettingsWindow +from AboutWindow import AboutWindow +import const + + +class WeCaseWindow(QtGui.QMainWindow, Ui_frm_MainWindow): + client = None + uid = None + timelineLoaded = QtCore.pyqtSignal(int) + imageLoaded = QtCore.pyqtSignal(str) + tabTextChanged = QtCore.pyqtSignal(int, str) + + def __init__(self, parent=None): + QtGui.QMainWindow.__init__(self, parent) + self.setupUi(self) + self.tweetViews = [self.homeView, self.mentionsView, self.commentsView, + self.myView] + self.setupModels() + self.setupMyUi() + self.loadConfig() + self.IMG_AVATAR = -2 + self.IMG_THUMB = -1 + self.notify = Notify(timeout=self.notify_timeout) + self.applyConfig() + + def init_account(self, client): + self.client = client + self.get_uid() + self.get_all_timeline() + self.get_my_timeline() + self.get_mentions_timeline() + self.get_comment_to_me() + + def loadConfig(self): + self.config = ConfigParser() + self.config.read(const.config_path) + + if not self.config.has_section('main'): + self.config['main'] = {} + + self.main_config = self.config['main'] + self.timer_interval = int(self.main_config.get('notify_interval', 30)) + self.notify_timeout = int(self.main_config.get('notify_timeout', 5)) + self.remindMentions = self.main_config.getboolean('remind_mentions', 1) + self.remindComments = self.main_config.getboolean('remind_comments', 1) + + def applyConfig(self): + try: + self.timer.stop_event.set() + except AttributeError: + pass + + self.timer = WTimer(self.timer_interval, self.show_notify) + self.timer.start() + self.notify.timeout = self.notify_timeout + + def setupMyUi(self): + for tweetView in self.tweetViews: + tweetView.setResizeMode(tweetView.SizeRootObjectToView) + tweetView.setSource( + QtCore.QUrl.fromLocalFile(const.myself_path + + "/ui/TweetList.qml")) + tweetView.rootContext().setContextProperty("mainWindow", self) + + @QtCore.pyqtSlot() + def load_more(self): + if self.tabWidget.currentIndex() == 0: + self.all_timeline_page += 1 + self.get_all_timeline(self.all_timeline_page) + elif self.tabWidget.currentIndex() == 1: + self.mentions_page += 1 + self.get_mentions_timeline(self.mentions_page) + elif self.tabWidget.currentIndex() == 2: + self.comment_to_me_page += 1 + self.get_comment_to_me(self.comment_to_me_page) + elif self.tabWidget.currentIndex() == 3: + self.my_timeline_page += 1 + self.get_my_timeline(self.my_timeline_page) + + def setupModels(self): + self.all_timeline = TweetModel(TweetItem(), self) + self.homeView.rootContext().setContextProperty("mymodel", + self.all_timeline) + self.mentions = TweetModel(TweetItem(), self) + self.mentionsView.rootContext().setContextProperty("mymodel", + self.mentions) + self.comment_to_me = TweetModel(TweetItem(), self) + self.commentsView.rootContext().setContextProperty("mymodel", + self.comment_to_me) + self.my_timeline = TweetModel(TweetItem(), self) + self.myView.rootContext().setContextProperty("mymodel", + self.my_timeline) + + def get_timeline(self, timeline, model, more=False): + for item in timeline: + model.appendRow(TweetItem(item)) + self.timelineLoaded.emit(more) + + def get_all_timeline(self, page=1, reset_remind=False, more=False): + all_timelines = self.client.statuses.home_timeline.get( + page=page).statuses + threading.Thread(group=None, target=self.get_timeline, + args=(all_timelines, self.all_timeline, more)).start() + self.all_timeline_page = page + if reset_remind: + self.tabWidget.setTabText(0, self.tr("Weibo")) + + def get_my_timeline(self, page=1, reset_remind=False, more=False): + my_timelines = self.client.statuses.user_timeline.get( + page=page).statuses + threading.Thread(group=None, target=self.get_timeline, + args=(my_timelines, self.my_timeline, more)).start() + self.my_timeline_page = page + + def get_mentions_timeline(self, page=1, reset_remind=False, more=False): + mentions_timelines = self.client.statuses.mentions.get( + page=page).statuses + threading.Thread(group=None, target=self.get_timeline, + args=( + mentions_timelines, self.mentions, more)).start() + self.mentions_page = page + if reset_remind: + self.client.remind.set_count.post(type="mention_status") + self.tabWidget.setTabText(1, self.tr("@ME")) + + def get_comment_to_me(self, page=1, reset_remind=False, more=False): + comments_to_me = self.client.comments.to_me.get(page=page).comments + threading.Thread(group=None, target=self.get_timeline, args=( + comments_to_me, self.comment_to_me, more)).start() + self.comment_to_me_page = page + if reset_remind: + self.client.remind.set_count.post(type="cmt") + self.tabWidget.setTabText(2, self.tr("Comments")) + + def get_remind(self, uid): + '''this function is used to get unread_count + from Weibo API. uid is necessary.''' + + reminds = self.client.remind.unread_count.get(uid=uid) + return reminds + + def get_uid(self): + '''How can I get my uid? here it is''' + try: + self.uid = self.client.account.get_uid.get().uid + except AttributeError: + return None + + def show_notify(self): + # This function is run in another thread by WTimer. + # Do not modify UI directly. Send signal and react it in a slot only. + # We use SIGNAL self.tabTextChanged and SLOT self.setTabText() + # to display unread count + + reminds = self.get_remind(self.uid) + msg = self.tr("You have:") + "\n" + num_msg = 0 + + if reminds['status'] != 0: + # Note: do NOT send notify here, or users will crazy. + self.tabTextChanged.emit(0, self.tr("Weibo(%d)") + % reminds['status']) + + if reminds['mention_status'] and self.remindMentions: + msg += self.tr("%d unread @ME") % reminds['mention_status'] + "\n" + self.tabTextChanged.emit(1, self.tr("@Me(%d)") + % reminds['mention_status']) + num_msg += 1 + + if reminds['cmt'] and self.remindComments: + msg += self.tr("%d unread comment(s)") % reminds['cmt'] + "\n" + self.tabTextChanged.emit(2, self.tr("Comments(%d)") + % reminds['cmt']) + num_msg += 1 + + if num_msg: + self.notify.showMessage(self.tr("WeCase"), msg) + + def setTabText(self, index, string): + self.tabWidget.setTabText(index, string) + + def moveToTop(self, more): + if more: + self.get_current_tweetView().rootObject().positionViewAtBeginning() + + def setLoaded(self, tweetid): + self.get_current_tweetView().rootObject().imageLoaded(tweetid) + + def showSettings(self): + wecase_settings = WeSettingsWindow() + if wecase_settings.exec_(): + self.loadConfig() + self.applyConfig() + + def showAbout(self): + wecase_about = AboutWindow() + wecase_about.exec_() + + def logout(self): + self.close() + # This is a model dialog, if we exec it before we close MainWindow + # MainWindow won't close + from LoginWindow import LoginWindow + wecase_login = LoginWindow() + wecase_login.exec_() + + def postTweet(self): + wecase_new = NewpostWindow() + wecase_new.client = self.client + wecase_new.exec_() + + @QtCore.pyqtSlot(str) + def comment(self, idstr): + wecase_new = NewpostWindow(action="comment", id=int(idstr)) + wecase_new.client = self.client + wecase_new.exec_() + + @QtCore.pyqtSlot(str, str) + def repost(self, idstr, text): + wecase_new = NewpostWindow(action="retweet", id=int(idstr), text=text) + wecase_new.client = self.client + wecase_new.exec_() + + @QtCore.pyqtSlot(str, result=int) + def favorite(self, idstr): + try: + self.client.favorites.create.post(id=int(idstr)) + return True + except: + return False + + @QtCore.pyqtSlot(str, result=bool) + def un_favorite(self, idstr): + try: + self.client.favorites.destroy.post(id=int(idstr)) + return True + except: + return False + + @QtCore.pyqtSlot(str, str) + def reply(self, idstr, cidstr): + wecase_new = NewpostWindow(action="reply", id=int(idstr), + cid=int(cidstr)) + wecase_new.client = self.client + wecase_new.exec_() + + @QtCore.pyqtSlot(str, str) + def look_orignal_pic(self, thumbnail_pic, tweetid): + threading.Thread(group=None, target=self.fetch_open_original_pic, + args=(thumbnail_pic, tweetid)).start() + + def fetch_open_original_pic(self, thumbnail_pic, tweetid): + """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.""" + # XXX: This function is NOT thread-safe! + # Click a single picture for many time will download a image for many + # times, and the picture may be overwrite, we will get a broken image. + + original_pic = thumbnail_pic.replace("thumbnail", + "large") # A simple trick ... ^_^ + localfile = const.cache_path + original_pic.split("/")[-1] + if not os.path.exists(localfile): + urllib.request.urlretrieve(original_pic, localfile) + + os.popen("xdg-open " + localfile) # xdg-open is common? + self.imageLoaded.emit(tweetid) + + def refresh(self): + model = self.get_current_model() + get_timeline = self.get_current_function() + + model.clear() + threading.Thread(group=None, target=get_timeline, + args=(1, True, True)).start() + + def get_current_tweetView(self): + tweetViews = {0: self.homeView, 1: self.mentionsView, + 2: self.commentsView, 3: self.myView} + return tweetViews[self.tabWidget.currentIndex()] + + def get_current_model(self): + models = {0: self.all_timeline, 1: self.mentions, + 2: self.comment_to_me, + 3: self.my_timeline} + return models[self.tabWidget.currentIndex()] + + def get_current_function(self): + functions = {0: self.get_all_timeline, 1: self.get_mentions_timeline, + 2: self.get_comment_to_me, 3: self.get_my_timeline} + return functions[self.tabWidget.currentIndex()] + + def closeEvent(self, event): + self.timer.stop_event.set() diff --git a/src/const.py b/src/const.py new file mode 100644 index 0000000..f7e5fe6 --- /dev/null +++ b/src/const.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +# WeCase -- This file defined constants and runtime constants. +# Copyright (C) 2013 Tom Li +# License: GPL v3 or later. + + +import sys +import os + + +APP_KEY = "1011524190" +APP_SECRET = "1898b3f668368b9f4a6f7ac8ed4a918f" +CALLBACK_URL = 'https://api.weibo.com/oauth2/default.html' +OAUTH2_PARAMETER = {'client_id': APP_KEY, + 'response_type': 'code', + 'redirect_uri': CALLBACK_URL, + 'action': 'submit', + 'userId': '', # username + 'passwd': '', # password + 'isLoginSina': 0, + 'from': '', + 'regCallback': '', + 'state': '', + 'ticket': '', + 'withOfficalFlag': 0} +config_path = os.environ['HOME'] + '/.config/wecase/config_db' +cache_path = os.environ['HOME'] + '/.cache/wecase/' +myself_name = sys.argv[0].split('/')[-1] +myself_path = os.path.abspath(sys.argv[0]).replace(myself_name, "") diff --git a/src/wecase.py b/src/wecase.py index d98c32e..6ec40c9 100755 --- a/src/wecase.py +++ b/src/wecase.py @@ -1,792 +1,33 @@ #!/usr/bin/env python3 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 -# WeCase -- Linux Sina Weibo Client -# Since 4th,Feb,2013 -# This is a TEST version -# Wait for ... +# WeCase -- Linux Sina Weibo Client, Since 4th, Feb, 2013. +# This is the main file of WeCase. # Copyright: GPL v3 or later. -# Well, Let's go! - import sys import os -import re -import webbrowser -import urllib.request -import urllib.parse -import urllib.error -import http.client -from configparser import ConfigParser -import notify2 as pynotify -import threading -from WTimer import WTimer -from weibo import APIClient, APIError from PyQt4 import QtCore, QtGui -from TweetUtils import tweetLength -from Tweet import TweetModel, TweetItem -from Smiley import SmileyModel, SmileyItem -from LoginWindow_ui import Ui_frm_Login -from MainWindow_ui import Ui_frm_MainWindow -from SettingWindow_ui import Ui_SettingWindow -from NewpostWindow_ui import Ui_NewPostWindow -from AboutWindow_ui import Ui_About_Dialog -from SmileyWindow_ui import Ui_SmileyWindow - -APP_KEY = "1011524190" -APP_SECRET = "1898b3f668368b9f4a6f7ac8ed4a918f" -CALLBACK_URL = 'https://api.weibo.com/oauth2/default.html' -OAUTH2_PARAMETER = {'client_id': APP_KEY, - 'response_type': 'code', - 'redirect_uri': CALLBACK_URL, - 'action': 'submit', - 'userId': '', # username - 'passwd': '', # password - 'isLoginSina': 0, - 'from': '', - 'regCallback': '', - 'state': '', - 'ticket': '', - 'withOfficalFlag': 0} -config_path = os.environ['HOME'] + '/.config/wecase/config_db' -cache_path = os.environ['HOME'] + '/.cache/wecase/' -myself_name = sys.argv[0].split('/')[-1] -myself_path = os.path.abspath(sys.argv[0]).replace(myself_name, "") - - -class LoginWindow(QtGui.QDialog, Ui_frm_Login): - loginReturn = QtCore.pyqtSignal(object) - - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.loadConfig() - self.setupUi(self) - self.setupMyUi() - self.setupSignals() - - def setupSignals(self): - # Other singals defined in Desinger. - self.loginReturn.connect(self.checkLogin) - self.chk_Remember.clicked.connect(self.uncheckAutoLogin) - - def accept(self, client): - if self.chk_Remember.isChecked(): - self.passwd[str(self.username)] = str(self.password) - self.last_login = str(self.username) - # Because this is a model dislog, - # closeEvent won't emit when we accept() the window, but will - # emit when we reject() the window. - self.saveConfig() - wecase_main = WeCaseWindow() - wecase_main.init_account(client) - wecase_main.show() - # Maybe users will logout, so reset the status - self.pushButton_log.setText(self.tr("GO!")) - self.pushButton_log.setEnabled(True) - self.done(True) - - def reject(self): - QtGui.QMessageBox.critical(None, self.tr("Authorize Failed!"), - self.tr("Check your account, " - "password and Internet Connection!")) - self.pushButton_log.setText(self.tr("GO!")) - self.pushButton_log.setEnabled(True) - - def checkLogin(self, client): - if client: - self.accept(client) - else: - self.reject() - - def setupMyUi(self): - self.show() - self.txt_Password.setEchoMode(QtGui.QLineEdit.Password) - self.cmb_Users.addItem(self.last_login) - - for username in list(self.passwd.keys()): - if username == self.last_login: - continue - self.cmb_Users.addItem(username) - - if self.cmb_Users.currentText(): - self.chk_Remember.setChecked(True) - self.setPassword(self.cmb_Users.currentText()) - - if self.auto_login: - self.chk_AutoLogin.setChecked(self.auto_login) - self.login() - - def loadConfig(self): - self.config = ConfigParser() - self.config.read(config_path) - - if not self.config.has_section('login'): - self.config['login'] = {} - - self.login_config = self.config['login'] - self.passwd = eval(self.login_config.get('passwd', "{}")) - self.last_login = str(self.login_config.get('last_login', "")) - self.auto_login = self.login_config.getboolean('auto_login', 0) - - def saveConfig(self): - self.login_config['passwd'] = str(self.passwd) - self.login_config['last_login'] = self.last_login - self.login_config['auto_login'] = str(self.chk_AutoLogin.isChecked()) - - with open(config_path, "w+") as config_file: - self.config.write(config_file) - - def login(self): - self.pushButton_log.setText(self.tr("Login, waiting...")) - self.pushButton_log.setEnabled(False) - self.ui_authorize() - - def ui_authorize(self): - self.username = self.cmb_Users.currentText() - self.password = self.txt_Password.text() - threading.Thread(group=None, target=self.authorize, - args=(self.username, self.password)).start() - - def authorize(self, username, password): - # TODO: This method is very messy, maybe do some cleanup? - - client = APIClient(app_key=APP_KEY, app_secret=APP_SECRET, - redirect_uri=CALLBACK_URL) - - # Step 1: Get the authorize url from Sina - authorize_url = client.get_authorize_url() - - # Step 2: Send the authorize info to Sina and get the authorize_code - # TODO: Rewrite them with urllib/urllib2 - oauth2 = OAUTH2_PARAMETER - oauth2['userId'] = username - oauth2['passwd'] = password - postdata = urllib.parse.urlencode(oauth2) - - conn = http.client.HTTPSConnection('api.weibo.com') - try: - conn.request('POST', '/oauth2/authorize', postdata, - {'Referer': authorize_url, - 'Content-Type': 'application/x-www-form-urlencoded'}) - except OSError: - self.loginReturn.emit(None) - return - - - res = conn.getresponse() - - location = res.getheader('location') - - if not location: - return self.loginReturn.emit(None) - - authorize_code = location.split('=')[1] - conn.close() - - # Step 3: Put the authorize information into SDK - r = client.request_access_token(authorize_code) - access_token = r.access_token - expires_in = r.expires_in - - client.set_access_token(access_token, expires_in) - self.loginReturn.emit(client) - - def setPassword(self, username): - if username: - self.txt_Password.setText(self.passwd[str(username)]) - - @QtCore.pyqtSlot(bool) - def uncheckAutoLogin(self, checked): - if not checked: - self.chk_AutoLogin.setChecked(False) - - def openRegisterPage(self): - webbrowser.open("http://weibo.com/signup/signup.php") - - def closeEvent(self, event): - # HACK: When a user want to close this window, closeEvent will emit. - # But if we don't have closeEvent, Qt will call reject(). We use - # reject() to show the error message, so users will see the error and - # they can not close this window. So just do nothing there to allow - # users to close the window. - pass - - -class WeCaseWindow(QtGui.QMainWindow, Ui_frm_MainWindow): - client = None - uid = None - timelineLoaded = QtCore.pyqtSignal(int) - imageLoaded = QtCore.pyqtSignal(str) - tabTextChanged = QtCore.pyqtSignal(int, str) - - def __init__(self, parent=None): - QtGui.QMainWindow.__init__(self, parent) - self.setupUi(self) - self.tweetViews = [self.homeView, self.mentionsView, self.commentsView, - self.myView] - self.setupModels() - self.setupMyUi() - self.loadConfig() - self.IMG_AVATAR = -2 - self.IMG_THUMB = -1 - self.notify = Notify(timeout=self.notify_timeout) - self.applyConfig() - - def init_account(self, client): - self.client = client - self.get_uid() - self.get_all_timeline() - self.get_my_timeline() - self.get_mentions_timeline() - self.get_comment_to_me() - - def loadConfig(self): - self.config = ConfigParser() - self.config.read(config_path) - - if not self.config.has_section('main'): - self.config['main'] = {} - - self.main_config = self.config['main'] - self.timer_interval = int(self.main_config.get('notify_interval', 30)) - self.notify_timeout = int(self.main_config.get('notify_timeout', 5)) - self.remindMentions = self.main_config.getboolean('remind_mentions', 1) - self.remindComments = self.main_config.getboolean('remind_comments', 1) - - def applyConfig(self): - try: - self.timer.stop_event.set() - except AttributeError: - pass - - self.timer = WTimer(self.timer_interval, self.show_notify) - self.timer.start() - self.notify.timeout = self.notify_timeout - - def setupMyUi(self): - for tweetView in self.tweetViews: - tweetView.setResizeMode(tweetView.SizeRootObjectToView) - tweetView.setSource( - QtCore.QUrl.fromLocalFile(myself_path + "/ui/TweetList.qml")) - tweetView.rootContext().setContextProperty("mainWindow", self) - - @QtCore.pyqtSlot() - def load_more(self): - if self.tabWidget.currentIndex() == 0: - self.all_timeline_page += 1 - self.get_all_timeline(self.all_timeline_page) - elif self.tabWidget.currentIndex() == 1: - self.mentions_page += 1 - self.get_mentions_timeline(self.mentions_page) - elif self.tabWidget.currentIndex() == 2: - self.comment_to_me_page += 1 - self.get_comment_to_me(self.comment_to_me_page) - elif self.tabWidget.currentIndex() == 3: - self.my_timeline_page += 1 - self.get_my_timeline(self.my_timeline_page) - - def setupModels(self): - self.all_timeline = TweetModel(TweetItem(), self) - self.homeView.rootContext().setContextProperty("mymodel", - self.all_timeline) - self.mentions = TweetModel(TweetItem(), self) - self.mentionsView.rootContext().setContextProperty("mymodel", - self.mentions) - self.comment_to_me = TweetModel(TweetItem(), self) - self.commentsView.rootContext().setContextProperty("mymodel", - self.comment_to_me) - self.my_timeline = TweetModel(TweetItem(), self) - self.myView.rootContext().setContextProperty("mymodel", - self.my_timeline) - - def get_timeline(self, timeline, model, more=False): - for item in timeline: - model.appendRow(TweetItem(item)) - self.timelineLoaded.emit(more) - - def get_all_timeline(self, page=1, reset_remind=False, more=False): - all_timelines = self.client.statuses.home_timeline.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=(all_timelines, self.all_timeline, more)).start() - self.all_timeline_page = page - if reset_remind: - self.tabWidget.setTabText(0, self.tr("Weibo")) - - def get_my_timeline(self, page=1, reset_remind=False, more=False): - my_timelines = self.client.statuses.user_timeline.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=(my_timelines, self.my_timeline, more)).start() - self.my_timeline_page = page - - def get_mentions_timeline(self, page=1, reset_remind=False, more=False): - mentions_timelines = self.client.statuses.mentions.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=( - mentions_timelines, self.mentions, more)).start() - self.mentions_page = page - if reset_remind: - self.client.remind.set_count.post(type="mention_status") - self.tabWidget.setTabText(1, self.tr("@ME")) - - def get_comment_to_me(self, page=1, reset_remind=False, more=False): - comments_to_me = self.client.comments.to_me.get(page=page).comments - threading.Thread(group=None, target=self.get_timeline, args=( - comments_to_me, self.comment_to_me, more)).start() - self.comment_to_me_page = page - if reset_remind: - self.client.remind.set_count.post(type="cmt") - self.tabWidget.setTabText(2, self.tr("Comments")) - - def get_remind(self, uid): - '''this function is used to get unread_count - from Weibo API. uid is necessary.''' - - reminds = self.client.remind.unread_count.get(uid=uid) - return reminds - - def get_uid(self): - '''How can I get my uid? here it is''' - try: - self.uid = self.client.account.get_uid.get().uid - except AttributeError: - return None - - def show_notify(self): - # This function is run in another thread by WTimer. - # Do not modify UI directly. Send signal and react it in a slot only. - # We use SIGNAL self.tabTextChanged and SLOT self.setTabText() - # to display unread count - - reminds = self.get_remind(self.uid) - msg = self.tr("You have:") + "\n" - num_msg = 0 - - if reminds['status'] != 0: - # Note: do NOT send notify here, or users will crazy. - self.tabTextChanged.emit(0, self.tr("Weibo(%d)") - % reminds['status']) - - if reminds['mention_status'] and self.remindMentions: - msg += self.tr("%d unread @ME") % reminds['mention_status'] + "\n" - self.tabTextChanged.emit(1, self.tr("@Me(%d)") - % reminds['mention_status']) - num_msg += 1 - - if reminds['cmt'] and self.remindComments: - msg += self.tr("%d unread comment(s)") % reminds['cmt'] + "\n" - self.tabTextChanged.emit(2, self.tr("Comments(%d)") - % reminds['cmt']) - num_msg += 1 - - if num_msg: - self.notify.showMessage(self.tr("WeCase"), msg) - - def setTabText(self, index, string): - self.tabWidget.setTabText(index, string) - - def moveToTop(self, more): - if more: - self.get_current_tweetView().rootObject().positionViewAtBeginning() - - def setLoaded(self, tweetid): - self.get_current_tweetView().rootObject().imageLoaded(tweetid) +from LoginWindow import LoginWindow +import const - def showSettings(self): - wecase_settings = WeSettingsWindow() - if wecase_settings.exec_(): - self.loadConfig() - self.applyConfig() - def showAbout(self): - wecase_about = AboutWindow() - wecase_about.exec_() - - def logout(self): - self.close() - # This is a model dialog, if we exec it before we close MainWindow - # MainWindow won't close - wecase_login.exec_() - - def postTweet(self): - wecase_new = NewpostWindow() - wecase_new.client = self.client - wecase_new.exec_() - - @QtCore.pyqtSlot(str) - def comment(self, idstr): - wecase_new = NewpostWindow(action="comment", id=int(idstr)) - wecase_new.client = self.client - wecase_new.exec_() - - @QtCore.pyqtSlot(str, str) - def repost(self, idstr, text): - wecase_new = NewpostWindow(action="retweet", id=int(idstr), text=text) - wecase_new.client = self.client - wecase_new.exec_() - - @QtCore.pyqtSlot(str, result=int) - def favorite(self, idstr): - try: - self.client.favorites.create.post(id=int(idstr)) - return True - except: - return False - - @QtCore.pyqtSlot(str, result=bool) - def un_favorite(self, idstr): - try: - self.client.favorites.destroy.post(id=int(idstr)) - return True - except: - return False - - @QtCore.pyqtSlot(str, str) - def reply(self, idstr, cidstr): - wecase_new = NewpostWindow(action="reply", id=int(idstr), - cid=int(cidstr)) - wecase_new.client = self.client - wecase_new.exec_() - - @QtCore.pyqtSlot(str, str) - def look_orignal_pic(self, thumbnail_pic, tweetid): - threading.Thread(group=None, target=self.fetch_open_original_pic, - args=(thumbnail_pic, tweetid)).start() - - def fetch_open_original_pic(self, thumbnail_pic, tweetid): - """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.""" - # XXX: This function is NOT thread-safe! - # Click a single picture for many time will download a image for many - # times, and the picture may be overwrite, we will get a broken image. - - original_pic = thumbnail_pic.replace("thumbnail", - "large") # A simple trick ... ^_^ - localfile = cache_path + original_pic.split("/")[-1] - if not os.path.exists(localfile): - urllib.request.urlretrieve(original_pic, localfile) - - os.popen("xdg-open " + localfile) # xdg-open is common? - self.imageLoaded.emit(tweetid) - - def refresh(self): - model = self.get_current_model() - get_timeline = self.get_current_function() - - model.clear() - threading.Thread(group=None, target=get_timeline, - args=(1, True, True)).start() - - def get_current_tweetView(self): - tweetViews = {0: self.homeView, 1: self.mentionsView, - 2: self.commentsView, 3: self.myView} - return tweetViews[self.tabWidget.currentIndex()] - - def get_current_model(self): - models = {0: self.all_timeline, 1: self.mentions, - 2: self.comment_to_me, - 3: self.my_timeline} - return models[self.tabWidget.currentIndex()] - - def get_current_function(self): - functions = {0: self.get_all_timeline, 1: self.get_mentions_timeline, - 2: self.get_comment_to_me, 3: self.get_my_timeline} - return functions[self.tabWidget.currentIndex()] - - def closeEvent(self, event): - self.timer.stop_event.set() - - -class WeSettingsWindow(QtGui.QDialog, Ui_SettingWindow): - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.setupUi(self) - self.loadConfig() - - def transformInterval(self, sliderValue): - return (sliderValue // 60, sliderValue % 60) - - def setIntervalText(self, sliderValue): - self.intervalLabel.setText(self.tr("%i min %i sec") % - (self.transformInterval(sliderValue))) - - def setTimeoutText(self, sliderValue): - self.timeoutLabel.setText(self.tr("%i sec") % sliderValue) - - def loadConfig(self): - self.config = ConfigParser() - self.config.read(config_path) - - if not self.config.has_section('main'): - self.config['main'] = {} - - self.main_config = self.config['main'] - self.intervalSlider.setValue(int(self.main_config.get( - 'notify_interval', "30"))) - self.setIntervalText(self.intervalSlider.value()) - self.timeoutSlider.setValue(int(self.main_config.get( - "notify_timeout", "5"))) - self.setTimeoutText(self.timeoutSlider.value()) - self.commentsChk.setChecked(self.main_config.getboolean( - "remind_comments", True)) - self.mentionsChk.setChecked(self.main_config.getboolean( - "remind_mentions", True)) - - def saveConfig(self): - self.config = ConfigParser() - self.config.read(config_path) - - if not self.config.has_section('main'): - self.config['main'] = {} - - self.main_config = self.config['main'] - self.main_config['notify_interval'] = str(self.intervalSlider.value()) - self.main_config['notify_timeout'] = str(self.timeoutSlider.value()) - self.main_config['remind_comments'] = str(self.commentsChk.isChecked()) - self.main_config['remind_mentions'] = str(self.mentionsChk.isChecked()) - - with open(config_path, "w+") as config_file: - self.config.write(config_file) - - def accept(self): - self.saveConfig() - self.done(True) - - def reject(self): - self.done(False) - - -class NewpostWindow(QtGui.QDialog, Ui_NewPostWindow): - client = None - image = None - apiError = QtCore.pyqtSignal(str) - sendSuccessful = QtCore.pyqtSignal() - - def __init__(self, parent=None, action="new", id=None, cid=None, text=""): - QtGui.QDialog.__init__(self, parent) - self.action = action - self.id = id - self.cid = cid - self.setupUi(self) - self.setupMyUi() - self.textEdit.setText(text) - self.textEdit.callback = self.mentions_suggest - self.textEdit.mention_flag = "@" - self.notify = Notify(timeout=1) - - def setupMyUi(self): - self.checkChars() - if self.action == "new": - self.chk_repost.setEnabled(False) - self.chk_comment.setEnabled(False) - self.chk_comment_original.setEnabled(False) - elif self.action == "retweet": - self.chk_repost.setEnabled(False) - self.pushButton_picture.setEnabled(False) - elif self.action == "comment": - self.chk_comment.setEnabled(False) - self.pushButton_picture.setEnabled(False) - elif self.action == "reply": - self.chk_repost.setEnabled(False) - self.chk_comment.setEnabled(False) - self.pushButton_picture.setEnabled(False) - - def mentions_suggest(self, text): - ret_users = [] - try: - word = re.findall('@[-a-zA-Z0-9_\u4e00-\u9fa5]+', text)[-1].replace('@', '') - except IndexError: - return [] - if not word.strip(): - return [] - users = self.client.search.suggestions.at_users.get(q=word, type=0) - for user in users: - ret_users.append("@" + user['nickname']) - return ret_users - - def send(self): - self.pushButton_send.setEnabled(False) - if self.action == "new": - threading.Thread(group=None, target=self.new).start() - elif self.action == "retweet": - threading.Thread(group=None, target=self.retweet).start() - elif self.action == "comment": - threading.Thread(group=None, target=self.comment).start() - elif self.action == "reply": - threading.Thread(group=None, target=self.reply).start() - - def retweet(self): - text = str(self.textEdit.toPlainText()) - try: - self.client.statuses.repost.post(id=int(self.id), status=text, - is_comment=int((self.chk_comment.isChecked() + - self.chk_comment_original.isChecked() * 2))) - self.notify.showMessage(self.tr("WeCase"), - self.tr("Retweet Success!")) - self.sendSuccessful.emit() - except APIError as e: - self.apiError.emit(str(e)) - return - - def comment(self): - text = str(self.textEdit.toPlainText()) - try: - self.client.comments.create.post(id=int(self.id), comment=text, - comment_ori=int(self.chk_comment_original.isChecked())) - if self.chk_repost.isChecked(): - self.client.statuses.repost.post(id=int(self.id), status=text) - self.notify.showMessage(self.tr("WeCase"), - self.tr("Comment Success!")) - self.sendSuccessful.emit() - except APIError as e: - self.apiError.emit(str(e)) - return - - def reply(self): - text = str(self.textEdit.toPlainText()) - try: - self.client.comments.reply.post(id=int(self.id), cid=int(self.cid), - comment=text, - comment_ori=int(self.chk_comment_original.isChecked())) - if self.chk_repost.isChecked(): - self.client.statuses.repost.post(id=int(self.id), status=text) - self.notify.showMessage(self.tr("WeCase"), - self.tr("Reply Success!")) - self.sendSuccessful.emit() - except APIError as e: - self.apiError.emit(str(e)) - return - - def new(self): - text = str(self.textEdit.toPlainText()) - - try: - if self.image: - self.client.statuses.upload.post(status=text, - pic=open(self.image, "rb")) - else: - self.client.statuses.update.post(status=text) - - self.notify.showMessage(self.tr("WeCase"), - self.tr("Tweet Success!")) - self.sendSuccessful.emit() - except APIError as e: - self.apiError.emit(str(e)) - return - - self.image = None - - def addImage(self): - ACCEPT_TYPE = self.tr("Images") + "(*.png *.jpg *.bmp *.gif)" - if self.image: - self.image = None - self.pushButton_picture.setText(self.tr("Picture")) - else: - self.image = QtGui.QFileDialog.getOpenFileName(self, - self.tr("Choose a" - " image"), - filter=ACCEPT_TYPE) - # user may cancel the dialog, so check again - if self.image: - self.pushButton_picture.setText(self.tr("Remove the picture")) - - def showError(self, e): - if "Text too long" in e: - QtGui.QMessageBox.warning(None, self.tr("Text too long!"), - self.tr("Please remove some text.")) - else: - QtGui.QMessageBox.warning(None, self.tr("Unknown error!"), e) - self.pushButton_send.setEnabled(True) - - def showSmiley(self): - wecase_smiley = SmileyWindow() - if wecase_smiley.exec_(): - self.textEdit.textCursor().insertText(wecase_smiley.smileyName) - - def checkChars(self): - '''Check textEdit's characters. - If it larger than 140, Send Button will be disabled - and label will show red chars.''' - - text = self.textEdit.toPlainText() - numLens = 140 - tweetLength(text) - if numLens == 140 and (not self.action == "retweet"): - # you can not send empty tweet, except retweet - self.pushButton_send.setEnabled(False) - elif numLens >= 0: - # length is okay - self.label.setStyleSheet("color:black;") - self.pushButton_send.setEnabled(True) - else: - # text is too long - self.label.setStyleSheet("color:red;") - self.pushButton_send.setEnabled(False) - self.label.setText(str(numLens)) - - -class Notify(): - image = myself_path + "/ui/img/WeCase 80.png" - - def __init__(self, appname=QtCore.QObject().tr("WeCase"), timeout=5): - pynotify.init(appname) - self.timeout = timeout - self.n = pynotify.Notification(appname) - - def showMessage(self, title, text): - self.n.update(title, text, self.image) - self.n.set_timeout(self.timeout * 1000) - self.n.show() - - -class AboutWindow(QtGui.QDialog, Ui_About_Dialog): - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.setupUi(self) - - -class SmileyWindow(QtGui.QDialog, Ui_SmileyWindow): - def __init__(self, parent=None): - QtGui.QDialog.__init__(self, parent) - self.setupUi(self) - self.setupMyUi() - self.setupModels() - self.smileyName = "" - - def setupMyUi(self): - self.smileyView.setResizeMode(self.smileyView.SizeRootObjectToView) - - def setupModels(self): - self.smileyModel = SmileyModel(self) - self.smileyModel.init_smileies(myself_path + "./ui/img/smiley", - self.smileyModel, SmileyItem) - self.smileyView.rootContext().setContextProperty("SmileyModel", - self.smileyModel) - self.smileyView.rootContext().setContextProperty("parentWindow", self) - self.smileyView.setSource(QtCore.QUrl.fromLocalFile( - myself_path + "/ui/SmileyView.qml")) - - @QtCore.pyqtSlot(str) - def returnSmileyName(self, smileyName): - self.smileyName = smileyName - self.done(True) - - -if __name__ == "__main__": +def mkconfig(): try: - os.mkdir(config_path.replace("/config_db", "")) + os.mkdir(const.config_path.replace("/config_db", "")) except OSError: pass try: - os.mkdir(cache_path) + os.mkdir(const.cache_path) except OSError: pass + +if __name__ == "__main__": + mkconfig() + app = QtGui.QApplication(sys.argv) # Qt's built-in string translator @@ -799,7 +40,7 @@ def returnSmileyName(self, smileyName): # WeCase's own string translator my_translator = QtCore.QTranslator(app) my_translator.load("WeCase_" + QtCore.QLocale.system().name(), - myself_path + "locale") + const.myself_path + "locale") app.installTranslator(my_translator) wecase_login = LoginWindow() From 04f369d2f858a38e0948c1d81dc6eab38554df52 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 21:40:08 +0800 Subject: [PATCH 32/38] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=B0=86=E5=BE=AE?= =?UTF-8?q?=E5=8D=9A=E8=8E=B7=E5=8F=96=E4=BB=A3=E7=A0=81=E7=A7=BB=E8=87=B3?= =?UTF-8?q?=20Model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LoginWindow.py | 3 +- src/Tweet.py | 128 +++++++++++++++++++++++++++++++++++++++++++- src/WeCaseWindow.py | 105 +++++++++++++----------------------- 3 files changed, 163 insertions(+), 73 deletions(-) diff --git a/src/LoginWindow.py b/src/LoginWindow.py index 4fa7314..0c79d5c 100644 --- a/src/LoginWindow.py +++ b/src/LoginWindow.py @@ -43,8 +43,7 @@ def accept(self, client): # closeEvent won't emit when we accept() the window, but will # emit when we reject() the window. self.saveConfig() - wecase_main = WeCaseWindow() - wecase_main.init_account(client) + wecase_main = WeCaseWindow(client) wecase_main.show() # Maybe users will logout, so reset the status self.pushButton_log.setText(self.tr("GO!")) diff --git a/src/Tweet.py b/src/Tweet.py index ce0fef0..87bb5a4 100644 --- a/src/Tweet.py +++ b/src/Tweet.py @@ -4,21 +4,26 @@ # WeCase -- This model implemented Model and Item for tweets # Copyright: GPL v3 or later. +import threading from PyQt4 import QtCore from datetime import datetime from TweetUtils import get_mid from WTimeParser import WTimeParser as time_parser -class TweetModel(QtCore.QAbstractListModel): +class TweetAbstractModel(QtCore.QAbstractListModel): def __init__(self, prototype, parent=None): - super(TweetModel, self).__init__() + super(TweetAbstractModel, self).__init__() self.setRoleNames(prototype.roles) self._tweets = [] def appendRow(self, item): self.insertRow(self.rowCount(), item) + def appendRows(self, items): + for item in items: + self.appendRow(TweetItem(item)) + def clear(self): self._tweets = [] @@ -30,10 +35,129 @@ def insertRow(self, row, item): self._tweets.insert(row, item) self.endInsertRows() + def insertRows(self, row, items): + for item in items: + self.insertRow(row, TweetItem(item)) + def rowCount(self, parent=QtCore.QModelIndex()): return len(self._tweets) +class TweetCommonModel(TweetAbstractModel): + timelineLoaded = QtCore.pyqtSignal() + + def __init__(self, prototype, timeline=None, parent=None): + super(TweetCommonModel, self).__init__(prototype, parent) + self.timeline = timeline + self.lock = False + + def _get_thread(self, page): + if self.lock: + return + self.lock = True + timeline = self.timeline.get(page=page).statuses + self.appendRows(timeline) + + self.since = int(self._tweets[0].id) + self.max = int(self._tweets[-1].id) + self.lock = False + + def _get(self, page): + threading.Thread(group=None, target=self._get_thread, + args=(page,)).start() + + def _new_thread(self, since): + if self.lock: + return + self.lock = True + timeline = self.timeline.get(since_id=since).statuses + self.insertRows(0, timeline) + + self.since = int(self._tweets[0].id) + self.lock = False + self.timelineLoaded.emit() + + def _new(self, since): + threading.Thread(group=None, target=self._new_thread, + args=(since,)).start() + + def _old_thread(self, max): + if self.lock: + return + self.lock = True + timeline = self.timeline.get(max_id=max).statuses + + # Remove the first same tweet + self.appendRows(timeline[1::]) + self.max = int(self._tweets[-1].id) + self.lock = False + + def _old(self, max): + threading.Thread(group=None, target=self._old_thread, + args=(max,)).start() + + + # Public + def load(self): + self._get(1) + self.page = 1 + + def next(self): + self._old(self.max) + + def new(self): + self.page = 1 + self._new(self.since) + + +class TweetCommentModel(TweetCommonModel): + def __init__(self, prototype, timeline=None, parent=None): + super(TweetCommentModel, self).__init__(prototype, timeline, parent) + + def _get_thread(self, page): + if self.lock: + return + self.lock = True + timeline = self.timeline.get(page=page).comments + self.appendRows(timeline) + + self.since = int(self._tweets[0].id) + self.max = int(self._tweets[-1].id) + self.lock = False + + def _get(self, page): + threading.Thread(group=None, target=self._get_thread, + args=(page,)).start() + + def _new_thread(self, since): + if self.lock: + return + timeline = self.timeline.get(since_id=since).comments + self.insertRows(0, timeline) + self.since = int(self._tweets[0].id) + self.lock = False + self.timelineLoaded.emit() + + def _new(self, since): + threading.Thread(group=None, target=self._new_thread, + args=(since,)).start() + + def _old_thread(self, max): + if self.lock: + return + self.lock = True + timeline = self.timeline.get(max_id=max).statuses + + # Remove the first same tweet + self.appendRows(timeline[1::]) + self.max = int(self._tweets[-1].id) + self.lock = False + + def _old(self, max): + threading.Thread(group=None, target=self._old_thread, + args=(max,)).start() + + class UserItem(QtCore.QObject): def __init__(self, item, parent=None): super(UserItem, self).__init__() diff --git a/src/WeCaseWindow.py b/src/WeCaseWindow.py index 7c32d0b..dfc6e5d 100644 --- a/src/WeCaseWindow.py +++ b/src/WeCaseWindow.py @@ -14,7 +14,7 @@ import threading from WTimer import WTimer from PyQt4 import QtCore, QtGui -from Tweet import TweetModel, TweetItem +from Tweet import TweetCommonModel, TweetCommentModel, TweetItem from MainWindow_ui import Ui_frm_MainWindow from Notify import Notify from NewpostWindow import NewpostWindow @@ -26,16 +26,17 @@ class WeCaseWindow(QtGui.QMainWindow, Ui_frm_MainWindow): client = None uid = None - timelineLoaded = QtCore.pyqtSignal(int) imageLoaded = QtCore.pyqtSignal(str) tabTextChanged = QtCore.pyqtSignal(int, str) - def __init__(self, parent=None): + def __init__(self, client, parent=None): QtGui.QMainWindow.__init__(self, parent) self.setupUi(self) self.tweetViews = [self.homeView, self.mentionsView, self.commentsView, self.myView] + self.client = client self.setupModels() + self.init_account() self.setupMyUi() self.loadConfig() self.IMG_AVATAR = -2 @@ -43,13 +44,8 @@ def __init__(self, parent=None): self.notify = Notify(timeout=self.notify_timeout) self.applyConfig() - def init_account(self, client): - self.client = client + def init_account(self): self.get_uid() - self.get_all_timeline() - self.get_my_timeline() - self.get_mentions_timeline() - self.get_comment_to_me() def loadConfig(self): self.config = ConfigParser() @@ -84,71 +80,42 @@ def setupMyUi(self): @QtCore.pyqtSlot() def load_more(self): - if self.tabWidget.currentIndex() == 0: - self.all_timeline_page += 1 - self.get_all_timeline(self.all_timeline_page) - elif self.tabWidget.currentIndex() == 1: - self.mentions_page += 1 - self.get_mentions_timeline(self.mentions_page) - elif self.tabWidget.currentIndex() == 2: - self.comment_to_me_page += 1 - self.get_comment_to_me(self.comment_to_me_page) - elif self.tabWidget.currentIndex() == 3: - self.my_timeline_page += 1 - self.get_my_timeline(self.my_timeline_page) + model = self.get_current_model() + model.next() def setupModels(self): - self.all_timeline = TweetModel(TweetItem(), self) + self.all_timeline = TweetCommonModel(TweetItem(), + self.client.statuses.home_timeline, + self) + self.all_timeline.load() self.homeView.rootContext().setContextProperty("mymodel", self.all_timeline) - self.mentions = TweetModel(TweetItem(), self) + self.mentions = TweetCommonModel(TweetItem(), + self.client.statuses.mentions, + self) + self.mentions.load() self.mentionsView.rootContext().setContextProperty("mymodel", self.mentions) - self.comment_to_me = TweetModel(TweetItem(), self) + self.comment_to_me = TweetCommentModel(TweetItem(), + self.client.comments.to_me, + self) + self.comment_to_me.load() self.commentsView.rootContext().setContextProperty("mymodel", self.comment_to_me) - self.my_timeline = TweetModel(TweetItem(), self) + self.my_timeline = TweetCommonModel(TweetItem(), + self.client.statuses.user_timeline, + self) + self.my_timeline.load() self.myView.rootContext().setContextProperty("mymodel", self.my_timeline) - def get_timeline(self, timeline, model, more=False): - for item in timeline: - model.appendRow(TweetItem(item)) - self.timelineLoaded.emit(more) - - def get_all_timeline(self, page=1, reset_remind=False, more=False): - all_timelines = self.client.statuses.home_timeline.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=(all_timelines, self.all_timeline, more)).start() - self.all_timeline_page = page - if reset_remind: + def reset_remind(self): + if self.tabWidget.currentIndex() == 0: self.tabWidget.setTabText(0, self.tr("Weibo")) - - def get_my_timeline(self, page=1, reset_remind=False, more=False): - my_timelines = self.client.statuses.user_timeline.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=(my_timelines, self.my_timeline, more)).start() - self.my_timeline_page = page - - def get_mentions_timeline(self, page=1, reset_remind=False, more=False): - mentions_timelines = self.client.statuses.mentions.get( - page=page).statuses - threading.Thread(group=None, target=self.get_timeline, - args=( - mentions_timelines, self.mentions, more)).start() - self.mentions_page = page - if reset_remind: + elif self.tabWidget.currentIndex() == 1: self.client.remind.set_count.post(type="mention_status") - self.tabWidget.setTabText(1, self.tr("@ME")) - - def get_comment_to_me(self, page=1, reset_remind=False, more=False): - comments_to_me = self.client.comments.to_me.get(page=page).comments - threading.Thread(group=None, target=self.get_timeline, args=( - comments_to_me, self.comment_to_me, more)).start() - self.comment_to_me_page = page - if reset_remind: + self.tabWidget.setTabText(1, self.tr("@Me")) + elif self.tabWidget.currentIndex() == 2: self.client.remind.set_count.post(type="cmt") self.tabWidget.setTabText(2, self.tr("Comments")) @@ -194,14 +161,14 @@ def show_notify(self): num_msg += 1 if num_msg: + return self.notify.showMessage(self.tr("WeCase"), msg) def setTabText(self, index, string): self.tabWidget.setTabText(index, string) - def moveToTop(self, more): - if more: - self.get_current_tweetView().rootObject().positionViewAtBeginning() + def moveToTop(self): + self.get_current_tweetView().rootObject().positionViewAtBeginning() def setLoaded(self, tweetid): self.get_current_tweetView().rootObject().imageLoaded(tweetid) @@ -289,11 +256,11 @@ def fetch_open_original_pic(self, thumbnail_pic, tweetid): def refresh(self): model = self.get_current_model() - get_timeline = self.get_current_function() - - model.clear() - threading.Thread(group=None, target=get_timeline, - args=(1, True, True)).start() + model.timelineLoaded.connect(self.moveToTop) + #model.clear() + #model.load() + model.new() + self.reset_remind() def get_current_tweetView(self): tweetViews = {0: self.homeView, 1: self.mentionsView, From 0484c660cb4c5b0060ff3fcc69354d8f108e18b3 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sat, 27 Apr 2013 21:43:10 +0800 Subject: [PATCH 33/38] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=97=B6=E5=8A=A0=E9=94=81=EF=BC=8C=E7=8E=B0=E5=9C=A8=E4=B8=BA?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/WeCaseWindow.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/WeCaseWindow.py b/src/WeCaseWindow.py index dfc6e5d..16422cf 100644 --- a/src/WeCaseWindow.py +++ b/src/WeCaseWindow.py @@ -43,6 +43,7 @@ def __init__(self, client, parent=None): self.IMG_THUMB = -1 self.notify = Notify(timeout=self.notify_timeout) self.applyConfig() + self.download_lock = [] def init_account(self): self.get_uid() @@ -241,16 +242,17 @@ def fetch_open_original_pic(self, thumbnail_pic, tweetid): 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.""" - # XXX: This function is NOT thread-safe! - # Click a single picture for many time will download a image for many - # times, and the picture may be overwrite, we will get a broken image. + if tweetid in self.download_lock: + return + self.download_lock.append(tweetid) original_pic = thumbnail_pic.replace("thumbnail", "large") # A simple trick ... ^_^ localfile = const.cache_path + original_pic.split("/")[-1] if not os.path.exists(localfile): urllib.request.urlretrieve(original_pic, localfile) + self.download_lock.remove(tweetid) os.popen("xdg-open " + localfile) # xdg-open is common? self.imageLoaded.emit(tweetid) From 3cd53541ff8d11ea7f1b4f76e0315b6ba9700967 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 28 Apr 2013 13:35:47 +0800 Subject: [PATCH 34/38] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=96=B0=E5=BE=AE=E5=8D=9A=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Tweet.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Tweet.py b/src/Tweet.py index 87bb5a4..9c4d11f 100644 --- a/src/Tweet.py +++ b/src/Tweet.py @@ -36,8 +36,10 @@ def insertRow(self, row, item): self.endInsertRows() def insertRows(self, row, items): + self.beginInsertRows(QtCore.QModelIndex(), row, row + len(items) - 1) for item in items: - self.insertRow(row, TweetItem(item)) + self._tweets.insert(row, TweetItem(item)) + self.endInsertRows() def rowCount(self, parent=QtCore.QModelIndex()): return len(self._tweets) @@ -70,7 +72,7 @@ def _new_thread(self, since): if self.lock: return self.lock = True - timeline = self.timeline.get(since_id=since).statuses + timeline = self.timeline.get(since_id=since).statuses[::-1] self.insertRows(0, timeline) self.since = int(self._tweets[0].id) @@ -132,7 +134,7 @@ def _get(self, page): def _new_thread(self, since): if self.lock: return - timeline = self.timeline.get(since_id=since).comments + timeline = self.timeline.get(since_id=since).comments[::-1] self.insertRows(0, timeline) self.since = int(self._tweets[0].id) self.lock = False From 9530de5c28dfa3b222213e32a3983e0982ff4f33 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Sun, 28 Apr 2013 14:09:01 +0800 Subject: [PATCH 35/38] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/WeCase_zh_CN.qm | Bin 5854 -> 5672 bytes locale/WeCase_zh_CN.ts | 73 ++++++++++++++++++++++------------------- src/wecase.pro | 5 ++- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/locale/WeCase_zh_CN.qm b/locale/WeCase_zh_CN.qm index 554d2006ebd59692747210227a0a8af23d3f88cd..669a960144aa72344f327c05c9918ca241f144e6 100644 GIT binary patch delta 695 zcmXAnX-HII6o%h>=iWPWmlq*BrqjIF1oiX8zdlA2`lyM%C(~1C};KIQU9vsVqG3<};qK?9a`mfniHk`XxZg zw2aN1r_pH1SWLq{nrGY}l z^Kh0kO56*R`s%OQ0|iQhbayIwwjn;r6=k~h0}WLw)9rLFM_EsfjdBB*t&gpP6gp}h zJ)6x&Nw$4zBkR02f5SV@Q)_Fxv6A1ntJc(d=6lr)J&AFGdLpfWlgFy%0S=<;T5^QX z!TH;?%uS&bn9?eCaT7AM`y+jTWkjoMjo}8qX!VFdL@zZD-&!OO?@mwIa0pm@2HJ@gS@#8)FXm{d3l=u8#-3*24Of4wZRc>n+a delta 866 zcmaixZA^_}7{~w5InVQ)=k2`IDb{EXcJwAQN@<3chDIe4N-uP@vn5$aFB)fsEhCjP zmDVt2j;v&-%zB-bK44nOtPiyj##tsK9~yDB^U((%p8cNdy07bh|F7$IuAU67_OUyG zkZ{1O3kb^xxTip@FQCoz&=>*i@&edysM?77O0bJ#0C5Pcnfk;Z@G9>Fls5<-W=UrW zf~VIK6A;q4jr6W!d+i{AV(f?trg#Q%8?I4&8;Pk^fSrlN;ePtj?e@?(!x-OY0CERo z{B)0ud}hKbX6fQtreq|ZxKAZbh;&}8>X@?A#X+h^GMUD_s=LHR=gU?7Resdxr#@)2 zk&$!i8|fDS{*rq7PZ=4`WEFohE<~|=p2fiqLo0hRb&Lc8*mpgRR2avp&kO*XI&Mu< z7zy@r;kEOCA(V@_A4I|x-2MmSfK<<2E^MayKCY{wh~ju|Om&@NH#c6tFd;l-j(8|G zc&J<9p>cpWt&FA#gm&KC5>3ul@i9+~BxLeX+vA}zi!T+X0BqU;y_CJlhbHou>UYv+xB%(y#}UeFC1+0*)t5_c zo9Bqh@}anIl;0tjPM!yh)$)*&R_^^=9H1ngd56-?5M@?*xjy|S zy{S^sGgW5YEYG{IK?Qdxd6@1Y}{-RiE(%1YXW4aWA-nZCfi{E diff --git a/locale/WeCase_zh_CN.ts b/locale/WeCase_zh_CN.ts index 2e80cd4..ea311c2 100644 --- a/locale/WeCase_zh_CN.ts +++ b/locale/WeCase_zh_CN.ts @@ -5,7 +5,7 @@ WeCase - 微盒 + 微盒 @@ -87,7 +87,7 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. LoginWindow - + Authorize Failed! 认证失败! @@ -97,17 +97,17 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 检查您的账号和密码! - + GO! 走起! - + Login, waiting... 登录中,请稍候…… - + Check your account, password and Internet Connection! 检查您的账号,密码和网络连接! @@ -173,27 +173,27 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. NewpostWindow - + WeCase 微盒 - + Retweet Success! 转发成功! - + Comment Success! 评论成功! - + Reply Success! 回复成功! - + Tweet Success! 发送成功! @@ -203,37 +203,37 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. 图片 (*.png *.jpg *.bmp *.gif) - + Picture 图片 - + Choose a image 选择一张图片 - + Remove the picture 移除图片 - + Text too long! 内容过长! - + Please remove some text. 请删除一些文字。 - + Unknown error! 未知错误! - + Images 图片 @@ -292,27 +292,27 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. TweetItem - + Time travel! 穿越时空! - + %.0f seconds ago %.0f 秒前 - + %.0f minutes ago %.0f 分钟前 - + %.0f hours ago %.0f 小时前 - + %.0f days ago %.0f 天前 @@ -320,67 +320,72 @@ This software is provided AS IS, and comes with ABSOLUTE NO WARRANTY. WeCaseWindow - + Weibo(%d) 微博(%d) - + @Me(%d) @我(%d) - + Comments(%d) 评论(%d) - + WeCase 微盒 - + Weibo 微博 @ME - @我 + @我 - + Comments 评论 - + You have: 您有: - + %d unread @ME %d 条未读 @提醒 - + %d unread comment(s) %d 条未读评论 + + + @Me + @我 + WeSettingsWindow %i min %i sec - %i 分 %i 秒 + %i 分 %i 秒 %i sec - %i 秒 + %i 秒 diff --git a/src/wecase.pro b/src/wecase.pro index 9847379..26083e5 100644 --- a/src/wecase.pro +++ b/src/wecase.pro @@ -1,6 +1,9 @@ SOURCES = wecase.py\ Smiley.py\ - Tweet.py + Tweet.py\ + NewpostWindow.py\ + WeCaseWindow.py\ + LoginWindow.py FORMS = ui/AboutWindow.ui\ ui/LoginWindow.ui\ From 9453d54c665eb8c46fd5a5f6ccce321e19419e3c Mon Sep 17 00:00:00 2001 From: Star Brilliant Date: Fri, 10 May 2013 00:20:33 +0800 Subject: [PATCH 36/38] Correct the file README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lines removed are: - 特别说明:在使用 l10n 框架之前,严禁任何人以任何理由翻译界面! - WARNING: Nobody is allowed to translate the UI until we use l10n framework!! --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 5921838..49fc115 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,6 @@ The Linux Sina Weibo Client This project is under heavy development. -**特别说明:在使用 l10n 框架之前,严禁任何人以任何理由翻译界面!** - -**WARNING: Nobody is allowed to translate the UI until we use l10n framework!!** - IRC Channel ------ \#wecase @ freenode From b1dcf201df53bfaabfaedcce9e9139232c28d3f6 Mon Sep 17 00:00:00 2001 From: Tom Li Date: Fri, 10 May 2013 20:40:20 +0800 Subject: [PATCH 37/38] Revert to Text because of performance issue --- res/ui/TweetDelegate.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/res/ui/TweetDelegate.qml b/res/ui/TweetDelegate.qml index eefd02c..bb34328 100644 --- a/res/ui/TweetDelegate.qml +++ b/res/ui/TweetDelegate.qml @@ -194,7 +194,8 @@ Item { onClicked: favoriteButtonClicked(); } - TextEdit { + Text { + //TextEdit { id: statusText color: "#333333" text: { @@ -215,8 +216,8 @@ Item { wrapMode: "Wrap" font.family: "Segoe UI" font.pointSize: 9 - selectByMouse: true - readOnly: true + //selectByMouse: true + //readOnly: true onLinkActivated: container.handleLink(link); } From 774c3e44588fc90910ea06195ee8f6eedef0337d Mon Sep 17 00:00:00 2001 From: Tom Li Date: Fri, 10 May 2013 20:40:55 +0800 Subject: [PATCH 38/38] =?UTF-8?q?=E6=8C=87=E5=AE=9A=20SSL=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BC=81=E5=9B=BE=E7=BB=95=E8=BF=87=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LoginWindow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/LoginWindow.py b/src/LoginWindow.py index 0c79d5c..05b609e 100644 --- a/src/LoginWindow.py +++ b/src/LoginWindow.py @@ -11,6 +11,8 @@ import urllib.parse import urllib.error import http.client +import ssl +import socket from configparser import ConfigParser import threading from weibo import APIClient @@ -130,6 +132,9 @@ def authorize(self, username, password): postdata = urllib.parse.urlencode(oauth2) conn = http.client.HTTPSConnection('api.weibo.com') + sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address) + conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file, ssl_version=ssl.PROTOCOL_TLSv1) + try: conn.request('POST', '/oauth2/authorize', postdata, {'Referer': authorize_url,