├── .gitignore ├── Credentials.pickle ├── LICENSE ├── README.md ├── build-demo.py ├── pyqt-demo.pdy ├── pyqt-demo.py ├── pyqt5_androiddeploy_instruct.rst └── pyqtclient.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Credentials.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bitmessage/PyBMAndroidQt/18f723c858eb227836906f566cf2a07ee8ac0189/Credentials.pickle -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bitmessage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyBMAndroidQt 2 | PyBitmessage API frontend for Android using QT5 and python 3 3 | -------------------------------------------------------------------------------- /build-demo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Riverbank Computing Limited 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, 8 | # this list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | # POSSIBILITY OF SUCH DAMAGE. 25 | 26 | 27 | import argparse 28 | import os 29 | import shutil 30 | import subprocess 31 | import sys 32 | 33 | 34 | def run(args): 35 | """ Run a command and terminate if it fails. """ 36 | print(args, '##################################### args in run method. ##################') 37 | try: 38 | ec = subprocess.call(' '.join(args), shell=True) 39 | except OSError as e: 40 | print("Execution failed:", e, file=sys.stderr) 41 | ec = 1 42 | 43 | if ec: 44 | sys.exit(ec) 45 | 46 | 47 | # Parse the command line. 48 | parser = argparse.ArgumentParser() 49 | parser.add_argument('--installed-qt-dir', 50 | help="the name of a directory containing pre-built Qt installations", 51 | metavar="DIR") 52 | parser.add_argument('--no-sysroot', help="do not build the sysroot", 53 | action='store_true') 54 | parser.add_argument('--source-dir', 55 | help="a directory containing the source packages", metavar="DIR", 56 | dest='source_dirs', action='append') 57 | parser.add_argument('--target', help="the target platform", default='') 58 | parser.add_argument('--quiet', help="disable progress messages", 59 | action='store_true') 60 | parser.add_argument('--verbose', help="enable verbose progress messages", 61 | action='store_true') 62 | cmd_line_args = parser.parse_args() 63 | build_sysroot = not cmd_line_args.no_sysroot 64 | installed_qt_dir = cmd_line_args.installed_qt_dir 65 | source_dirs = cmd_line_args.source_dirs 66 | target = cmd_line_args.target 67 | quiet = cmd_line_args.quiet 68 | verbose = cmd_line_args.verbose 69 | 70 | # Pick a default target if none is specified. 71 | if not target: 72 | if sys.platform == 'win32': 73 | # MSVC2015 is v14, MSVC2017 is v15. 74 | vs_major = os.environ.get('VisualStudioVersion', '0.0').split('.')[0] 75 | 76 | if vs_major == '15': 77 | is_32 = (os.environ.get('VSCMD_ARG_TGT_ARCH') != 'x64') 78 | elif vs_major == '14': 79 | is_32 = (os.environ.get('Platform') != 'X64') 80 | else: 81 | # Default to 64 bits. 82 | is_32 = False 83 | 84 | target = 'win-' + ('32' if is_32 else '64') 85 | elif sys.platform == 'darwin': 86 | target = 'macos-64' 87 | elif sys.platform.startswith('linux'): 88 | import struct 89 | 90 | target = 'linux-{0}'.format(8 * struct.calcsize('P')) 91 | else: 92 | print("Unsupported platform:", sys.platform, file=sys.stderr) 93 | sys.exit(2) 94 | 95 | # Make sure the Qt directory was specified if it is needed. 96 | if target in ('android-32', 'ios-64') and not installed_qt_dir: 97 | print("--installed-qt-dir must be specified for", target, file=sys.stderr) 98 | sys.exit(2) 99 | 100 | # Create the list of directories to search for source packages and Qt. 101 | if not source_dirs: 102 | source_dirs = ['.'] 103 | 104 | if installed_qt_dir: 105 | source_dirs.insert(0, installed_qt_dir) 106 | 107 | source_dirs = [os.path.abspath(s) for s in source_dirs] 108 | 109 | # Anchor everything from the directory containing this script. 110 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 111 | 112 | sysroot_dir = 'sysroot-' + target 113 | print("sysroot_dir in build-demo.py================================",sysroot_dir) 114 | build_dir = 'build-' + target 115 | host_bin_dir = os.path.abspath(os.path.join(sysroot_dir, 'host', 'bin')) 116 | print("host_bin_dir in build-demo.py==================================",host_bin_dir) 117 | 118 | # Build sysroot. 119 | if build_sysroot: 120 | args = ['pyqtdeploy-sysroot', '--target', target, '--sysroot', sysroot_dir] 121 | print("args in build-demo.py============================",args) 122 | print("sysroot_dir in build-demo.py============================",sysroot_dir) 123 | 124 | print("source_dirs in build-demo.py=================================",source_dirs) 125 | for s in source_dirs: 126 | args.append('--source-dir') 127 | args.append(s) 128 | print("args in build-demo.py=================================",args) 129 | 130 | if quiet: 131 | args.append('--quiet') 132 | 133 | if verbose: 134 | args.append('--verbose') 135 | 136 | args.append('sysroot.json') 137 | 138 | run(args) 139 | 140 | # Build the demo. 141 | shutil.copy('pyqt-demo.py', os.path.join('data', 'pyqt-demo.py.dat')) 142 | 143 | run(['pyqtdeploy-build', '--target', target, '--sysroot', sysroot_dir, 144 | '--build-dir', build_dir, 'pyqt-demo.pdy']) 145 | 146 | # Run qmake. Use the qmake left by pyqtdeploy-sysroot. 147 | os.chdir(build_dir) 148 | run([os.path.join(host_bin_dir, 'qmake')]) 149 | 150 | # Run make. (When targeting iOS we leave it to Xcode.) 151 | if target.startswith('ios'): 152 | pass 153 | else: 154 | # We only support MSVC on Windows. 155 | make = 'nmake' if sys.platform == 'win32' else 'make' 156 | 157 | run([make]) 158 | 159 | if target.startswith('android'): 160 | run([make, 'INSTALL_ROOT=deploy', 'install']) 161 | run([os.path.join(host_bin_dir, 'androiddeployqt'), '--input', 162 | 'android-libpyqt-demo.so-deployment-settings.json', '--output', 163 | 'deploy']) 164 | 165 | # Tell the user where the demo is. 166 | if target.startswith('android'): 167 | apk_dir = os.path.join(build_dir, 'deploy', 'bin') 168 | print("""The QtApp-debug.apk file can be found in the '{0}' 169 | directory. Run adb to install it to a simulator.""".format(apk_dir)) 170 | 171 | elif target.startswith('ios'): 172 | print("""The pyqt-demo.xcodeproj file can be found in the '{0}' directory. 173 | Run Xcode to build the app and run it in the simulator or deploy it to a 174 | device.""".format(build_dir)) 175 | 176 | elif target.startswith('win') or sys.platform == 'win32': 177 | print("The pyqt-demo executable can be found in the '{0}' directory.".format(os.path.join(build_dir, 'release'))) 178 | 179 | else: 180 | print("The pyqt-demo executable can be found in the '{0}' directory.".format(build_dir)) 181 | -------------------------------------------------------------------------------- /pyqt-demo.pdy: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pyqt-demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import xmlrpc.client 3 | import base64 4 | import pickle 5 | import logging 6 | import json 7 | from PyQt5 import QtCore, QtWidgets 8 | from PyQt5.QtWidgets import (QApplication, QPushButton,QDialog,QFormLayout,QGraphicsDropShadowEffect, 9 | QHBoxLayout,QTabWidget,QMessageBox,QToolBar,QProgressBar, 10 | QFrame,QGridLayout,QSizePolicy, QSpacerItem, QToolButton,QCompleter, 11 | QVBoxLayout,QMenuBar,QStyleFactory,QMainWindow, QWidget, QTextEdit, 12 | QLineEdit,QToolButton,QAction,QSplitter,QComboBox,QFileDialog,QPlainTextEdit, 13 | QTableWidgetItem, QRadioButton,QLabel,QTableWidget,QListWidget,QListWidgetItem) 14 | from PyQt5.QtCore import QCoreApplication 15 | from PyQt5.QtCore import QSize, QRect,Qt 16 | from PyQt5.QtGui import QRegExpValidator,QIcon, QPixmap, QColor 17 | 18 | class ToolBars(QWidget): 19 | def __init__ (self): 20 | super(ToolBars, self).__init__() 21 | self.tools = QToolBar() 22 | self.tools.showMaximized() 23 | self.toolbar = QToolButton() 24 | self.toolbar.showMaximized() 25 | self.toolbar.setIcon(QIcon("images/images.png")) 26 | # self.toolbar.setIconSize(QSize(800,800)) 27 | self.toolbar.setPopupMode(QToolButton.InstantPopup) 28 | self.toolbar.setAutoRaise( True ) 29 | self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) 30 | self.toolbar.setCheckable( True ) 31 | self.tools.addWidget(self.toolbar) 32 | self.toolbar.setFixedSize(100,100) 33 | 34 | self.inbox = QAction(QIcon("images/sign.png"),"Inbox",self) 35 | self.sends = QAction(QIcon("images/sign.png"),"Send",self) 36 | self.create = QAction(QIcon("images/sign.png"),"Create",self) 37 | self.trash = QAction(QIcon("images/sign.png"),"Trash",self) 38 | self.draft = QAction(QIcon("images/sign.png"),"Draft",self) 39 | self.toolbar.addAction(self.inbox) 40 | self.toolbar.addAction(self.create) 41 | self.toolbar.addAction(self.sends) 42 | self.toolbar.addAction(self.trash) 43 | self.toolbar.addAction(self.draft) 44 | self.tools.setStyleSheet(''' 45 | color: rgb(0, 0, 255); 46 | font-size: 100; 47 | font-weight: 1000; 48 | ''') 49 | self.toolbar.setStyleSheet(''' 50 | background-color: rgb(255, 0, 0); 51 | ''') 52 | 53 | 54 | self.combo=QComboBox() 55 | self.combo.setMaximumWidth(800) 56 | self.combo.setFixedHeight(200) 57 | ides = api.listAddresses() 58 | new_ides = json.loads(ides) 59 | list_ides = new_ides['addresses'] 60 | list_com = [] 61 | for address in list_ides: 62 | ids = address['address'] 63 | labels = address['label'] 64 | add_label2 = labels +"<"+ids+">" 65 | self.combo.addItem(add_label2) 66 | self.tools.addWidget(self.combo) 67 | 68 | 69 | 70 | class QCustomQWidget (QWidget): 71 | def __init__ (self, parent = None): 72 | super(QCustomQWidget, self).__init__(parent) 73 | self.textQVBoxLayout = QVBoxLayout() 74 | self.textUpQLabel = QLabel() 75 | self.textDownQLabel = QLabel() 76 | self.textQVBoxLayout.addWidget(self.textUpQLabel) 77 | self.textQVBoxLayout.addWidget(self.textDownQLabel) 78 | self.allQHBoxLayout = QHBoxLayout() 79 | self.iconQLabel = QLabel() 80 | self.allQHBoxLayout.addWidget(self.iconQLabel, 0) 81 | self.allQHBoxLayout.addLayout(self.textQVBoxLayout, 1) 82 | self.setLayout(self.allQHBoxLayout) 83 | # setStyleSheet 84 | self.textUpQLabel.setStyleSheet(''' 85 | color: rgb(0, 0, 255); 86 | ''') 87 | self.textDownQLabel.setStyleSheet(''' 88 | color: rgb(255, 0, 0); 89 | ''') 90 | 91 | def setTextUp(self, text): 92 | self.textUpQLabel.setText(text) 93 | 94 | def setTextDown(self, text): 95 | self.textDownQLabel.setText(text) 96 | 97 | def setIcon(self, imagePath): 98 | self.iconQLabel.setPixmap(QPixmap(imagePath)) 99 | 100 | def cleared(self): 101 | item = self.allQHBoxLayout 102 | if item != None : 103 | widget = item.widget() 104 | if widget != None: 105 | self.allQHBoxLayout.removeWidget(widget) 106 | widget.deleteLater() 107 | 108 | class TrashWindow(ToolBars): 109 | def __init__(self, parent=None): 110 | super(TrashWindow, self).__init__() 111 | self.widget = QWidget(self) 112 | 113 | fbox = QFormLayout(self) 114 | fbox.addRow(self.tools) 115 | 116 | l1 = QLabel("To: ",self) 117 | self.add1 = QLineEdit(self) 118 | self.add1.setText("BM-2cWyUfBdY2FbgyuCb7abFZ49JYxSzUhNFe") 119 | fbox.addRow(l1,self.add1) 120 | 121 | l2 = QLabel("From: ",self) 122 | self.add2 = QLineEdit() 123 | self.add2.setText("BM-2cV5v5bqzVznBXk3fsLaAfPkCnC42Dhwc4") 124 | fbox.addRow(l2,self.add2) 125 | 126 | class DraftWindow(ToolBars): 127 | def __init__(self, parent=None): 128 | super(DraftWindow, self).__init__() 129 | fbox = QVBoxLayout(self) 130 | fbox.addWidget(self.tools) 131 | 132 | self.myQListWidget = QListWidget(self) 133 | self.myQListWidget.showMaximized() 134 | for name, message, icon in [ 135 | ('Meyoko', 'hello...', 'images/sign.png'), 136 | ('Nyaruko', 'how are you..', 'images/sign.png'), 137 | ('Louise', 'Dear friend', 'images/sign.png'), 138 | ('mily', 'hello...', 'images/sign.png')]: 139 | # Create QCustomQWidget 140 | myQCustomQWidget = QCustomQWidget() 141 | myQCustomQWidget.setTextUp(name) 142 | myQCustomQWidget.setTextDown(message) 143 | myQCustomQWidget.setIcon(icon) 144 | # Create QListWidgetItem 145 | myQListWidgetItem = QListWidgetItem(self.myQListWidget) 146 | # Set size hint 147 | myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint()) 148 | # Add QListWidgetItem into QListWidget 149 | self.myQListWidget.addItem(myQListWidgetItem) 150 | self.myQListWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget) 151 | fbox.removeWidget(self.myQListWidget) 152 | fbox.addWidget(self.myQListWidget) 153 | 154 | class CreateWindow(ToolBars): 155 | def __init__(self, parent=None): 156 | super(CreateWindow, self).__init__() 157 | self.widget = QWidget(self) 158 | fbox = QFormLayout(self) 159 | fbox.addRow(self.tools) 160 | 161 | l1 = QLabel("To: ",self) 162 | self.add1 = QComboBox(self) 163 | self.add1.setMaximumWidth(800) 164 | self.add1.setFixedHeight(100) 165 | self.add1.setCurrentText('choose') 166 | self.add1.currentTextChanged.connect(self.selectionchange) 167 | to_add = api.listAddressBookEntries() 168 | new_to = json.loads(to_add) 169 | list_to = new_to['addresses'] 170 | for address in list_to: 171 | add1 = address['address'] 172 | label1 = base64.b64decode(address['label']).decode("utf-8") 173 | add_label1 = label1 +"<"+add1+">" 174 | self.add1.addItem(add_label1) 175 | fbox.addRow(l1,self.add1) 176 | 177 | l2 = QLabel("From: ",self) 178 | self.add2 = QComboBox() 179 | self.add2.setMaximumWidth(800) 180 | self.add2.setFixedHeight(100) 181 | self.add2.setCurrentText('choose') 182 | self.add2.currentTextChanged.connect(self.selectionchange1) 183 | from_add = api.listAddresses() 184 | new_from = json.loads(from_add) 185 | list_from = new_from['addresses'] 186 | for address in list_from: 187 | add2 = address['address'] 188 | label2 = address['label'] 189 | add_label2 = label2 +"<"+add2+">" 190 | self.add2.addItem(add_label2) 191 | fbox.addRow(l2,self.add2) 192 | 193 | l4 = QLabel("Subject: ",self) 194 | self.add4 = QLineEdit(self) 195 | fbox.addRow(l4,self.add4) 196 | 197 | l3 = QLabel("Message: ",self) 198 | fbox.addRow(l3) 199 | self.add3 = QTextEdit() 200 | fbox.addRow(self.add3) 201 | 202 | self.send = QPushButton(self) 203 | self.send.setText("SEND") 204 | self.send.clicked.connect(self.addition) 205 | fbox.addRow(self.send) 206 | 207 | def selectionchange1(self,i): 208 | sub = i[i.find('<')+1:i.find('>')] 209 | self.fromAddress = sub 210 | 211 | def selectionchange(self,i): 212 | sub = i[i.find('<')+1:i.find('>')] 213 | self.toAddress = sub 214 | 215 | def addition(self): 216 | subject = base64.b64encode(bytes(self.add4.text(), 'utf-8')).decode("utf-8") 217 | message = base64.b64encode(bytes(self.add3.toPlainText(), 'utf-8')).decode("utf-8") 218 | sums = api.sendMessage(self.toAddress, self.fromAddress, subject, message) 219 | 220 | 221 | class InboxWindow(ToolBars): 222 | def __init__(self, parent=None,is_change= None,id_default = "BM-2cXtYXvA1yZ1KdjHufxqDEGPwd3Xt4JEdk"): 223 | super(InboxWindow, self).__init__() 224 | self.widget = QWidget(self) 225 | 226 | fbox = QVBoxLayout(self) 227 | fbox.addWidget(self.tools) 228 | if is_change is not None: 229 | self.combo.clear() 230 | self.combo.addItem(is_change) 231 | ides = api.listAddresses() 232 | new_ides = json.loads(ides) 233 | list_ides = new_ides['addresses'] 234 | list_com = [] 235 | for address in list_ides: 236 | ids = address['address'] 237 | labels = address['label'] 238 | add_label2 = labels +"<"+ids+">" 239 | if is_change != add_label2: 240 | self.combo.addItem(add_label2) 241 | else: 242 | pass 243 | 244 | self.myQListWidget = QListWidget(self) 245 | self.myQListWidget.showMaximized() 246 | newids = base64.b64encode(bytes(id_default, 'utf-8')).decode("utf-8") 247 | messages = api.getInboxMessagesByAddress(id_default) 248 | new_messages = json.loads(messages) 249 | inbox_list = new_messages['inboxMessages'] 250 | 251 | for inbox in inbox_list: 252 | add_from = inbox['fromAddress'] 253 | subject = base64.b64decode(inbox['subject']).decode("utf-8") 254 | # Create QCustomQWidget 255 | myQCustomQWidget = QCustomQWidget() 256 | myQCustomQWidget.setTextUp(add_from) 257 | myQCustomQWidget.setTextDown(subject) 258 | # Create QListWidgetItem 259 | myQListWidgetItem = QListWidgetItem(self.myQListWidget) 260 | # Set size hint 261 | myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint()) 262 | # Add QListWidgetItem into QListWidget 263 | self.myQListWidget.addItem(myQListWidgetItem) 264 | self.myQListWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget) 265 | fbox.addWidget(self.myQListWidget) 266 | 267 | class SendWindow(ToolBars): 268 | def __init__(self, parent=None,is_change= None,id_default = "BM-2cXtYXvA1yZ1KdjHufxqDEGPwd3Xt4JEdk"): 269 | super(SendWindow, self).__init__() 270 | self.widget = QWidget(self) 271 | 272 | fbox = QVBoxLayout(self) 273 | fbox.addWidget(self.tools) 274 | if is_change is not None: 275 | self.combo.clear() 276 | self.combo.addItem(is_change) 277 | ides = api.listAddresses() 278 | new_ides = json.loads(ides) 279 | list_ides = new_ides['addresses'] 280 | list_com = [] 281 | for address in list_ides: 282 | ids = address['address'] 283 | labels = address['label'] 284 | add_label2 = labels +"<"+ids+">" 285 | if is_change != add_label2: 286 | self.combo.addItem(add_label2) 287 | else: 288 | pass 289 | 290 | self.myQListWidget = QListWidget(self) 291 | self.myQListWidget.setVisible(False) 292 | self.myQListWidget.setUpdatesEnabled(True) 293 | self.myQListWidget.showMaximized() 294 | messages = api.getSentMessagesByAddress(id_default) 295 | new_messages = json.loads(messages) 296 | inbox_list = new_messages['sentMessages'] 297 | 298 | for inbox in inbox_list: 299 | add_from = inbox['toAddress'] 300 | subject = base64.b64decode(inbox['subject']).decode("utf-8") 301 | # Create QCustomQWidget 302 | myQCustomQWidget = QCustomQWidget() 303 | myQCustomQWidget.setTextUp(add_from) 304 | myQCustomQWidget.setTextDown(subject) 305 | # Create QListWidgetItem 306 | myQListWidgetItem = QListWidgetItem(self.myQListWidget) 307 | # Set size hint 308 | myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint()) 309 | # Add QListWidgetItem into QListWidget 310 | self.myQListWidget.addItem(myQListWidgetItem) 311 | self.myQListWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget) 312 | fbox.addWidget(self.myQListWidget) 313 | 314 | 315 | class UIToolTab(QWidget): 316 | def __init__(self, parent=None): 317 | super(UIToolTab, self).__init__() 318 | self.widget = QWidget(self) 319 | fbox = QFormLayout(self) 320 | fbox.setContentsMargins(100, 200, 100, 100) 321 | 322 | 323 | 324 | l1 = QLabel("Username: ",self) 325 | self.add1 = QLineEdit(self) 326 | fbox.addRow(l1,self.add1) 327 | 328 | l2 = QLabel("Password: ",self) 329 | self.add2 = QLineEdit(self) 330 | fbox.addRow(l2,self.add2) 331 | 332 | l4 = QLabel("IP Address: ",self) 333 | self.add4 = QLineEdit(self) 334 | fbox.addRow(l4,self.add4) 335 | 336 | l3 = QLabel("Port: ",self) 337 | self.add3 = QLineEdit(self) 338 | fbox.addRow(l3,self.add3) 339 | 340 | try: 341 | pickle_off = open("Credentials.json","r") 342 | emp = json.load(pickle_off) 343 | self.add1.setText(emp['username']) 344 | self.add2.setText(emp['password']) 345 | self.add4.setText(emp['ip']) 346 | self.add3.setText(emp['port']) 347 | except: 348 | pass 349 | 350 | self.connect = QPushButton(self) 351 | self.connect.setText("CONNECT") 352 | self.save = QPushButton(self) 353 | self.save.setText("SAVE") 354 | fbox.addRow(self.save,self.connect) 355 | 356 | 357 | class MainWindow(QMainWindow): 358 | def __init__(self, parent=None): 359 | super(MainWindow, self).__init__(parent) 360 | self.changes_menu = 'Inbox' 361 | self.ToolTab = UIToolTab(self) 362 | self.startUIToolTab() 363 | 364 | def startUIToolTab(self): 365 | self.setWindowTitle("CONNECT") 366 | self.setCentralWidget(self.ToolTab) 367 | self.ToolTab.connect.clicked.connect(self.verify) 368 | self.ToolTab.save.clicked.connect(self.save_data) 369 | self.show() 370 | 371 | def save_data(self): 372 | user = self.ToolTab.add1.text() 373 | password = self.ToolTab.add2.text() 374 | ip = self.ToolTab.add4.text() 375 | port = self.ToolTab.add3.text() 376 | emp = {'username':user,'password':password,'ip':ip,'port':port} 377 | credentials = open("Credentials.json","w") 378 | json.dump(emp, credentials) 379 | 380 | def verify(self): 381 | global api 382 | user = self.ToolTab.add1.text() 383 | password = self.ToolTab.add2.text() 384 | ip = self.ToolTab.add4.text() 385 | port = self.ToolTab.add3.text() 386 | api = xmlrpc.client.ServerProxy("http://%s:%s@%s:%s/" %(user,password,ip,port)) 387 | sums = api.add(2,3) 388 | if isinstance(sums, int): 389 | self.startInbox() 390 | else: 391 | choice = QMessageBox.question(self, 'Incorrect', 392 | "You have Put Incorrect Credentials", 393 | QMessageBox.Yes) 394 | if choice == QMessageBox.Yes: 395 | self.ToolTab.add1.setText("") 396 | self.ToolTab.add2.setText("") 397 | self.ToolTab.add4.setText("") 398 | self.ToolTab.add3.setText("") 399 | 400 | 401 | def startInbox(self): 402 | self.Window = InboxWindow(self) 403 | self.setWindowTitle("Inbox") 404 | self.setCentralWidget(self.Window) 405 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 406 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 407 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 408 | self.show() 409 | 410 | def startSend(self): 411 | self.Window = SendWindow(self) 412 | self.setWindowTitle("Send") 413 | self.setCentralWidget(self.Window) 414 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 415 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 416 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 417 | self.show() 418 | 419 | def startCreate(self): 420 | self.Window = CreateWindow(self) 421 | self.setWindowTitle("Create") 422 | self.setCentralWidget(self.Window) 423 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 424 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 425 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 426 | self.show() 427 | 428 | def startDraft(self): 429 | self.Window = DraftWindow(self) 430 | self.setWindowTitle("Draft") 431 | self.setCentralWidget(self.Window) 432 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 433 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 434 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 435 | self.show() 436 | 437 | def startTrash(self): 438 | self.Window = TrashWindow(self) 439 | self.setWindowTitle("Trash") 440 | self.setCentralWidget(self.Window) 441 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 442 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 443 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 444 | self.show() 445 | 446 | def changedItem(self,folder,comb): 447 | if folder == 'Inbox': 448 | self.Window = InboxWindow(self,is_change=comb,id_default = self.select_ids) 449 | self.setWindowTitle("Inbox") 450 | if folder == 'Send': 451 | self.Window = SendWindow(self,is_change=comb,id_default = self.select_ids) 452 | self.setWindowTitle("Send") 453 | self.setCentralWidget(self.Window) 454 | QApplication.setStyle(QStyleFactory.create('Cleanlooks')) 455 | self.Window.toolbar.triggered[QAction].connect(self.toolbtnpressed) 456 | self.Window.combo.currentTextChanged.connect(self.selectionchange_id) 457 | self.show() 458 | 459 | 460 | def toolbtnpressed(self,a): 461 | if a.text() == 'Inbox': 462 | self.changes_menu = 'Inbox' 463 | self.startInbox() 464 | if a.text() == 'Create': 465 | self.startCreate() 466 | if a.text() == 'Send': 467 | self.changes_menu = 'Send' 468 | self.startSend() 469 | if a.text() == 'Draft': 470 | self.startDraft() 471 | if a.text() == 'Trash': 472 | self.startTrash() 473 | 474 | def selectionchange_id(self,ids): 475 | self.select_ids = ids[ids.find('<')+1:ids.find('>')] 476 | if self.changes_menu == 'Inbox': 477 | self.changedItem('Inbox',ids) 478 | 479 | if self.changes_menu == 'Send': 480 | self.changedItem('Send',ids) 481 | 482 | 483 | 484 | if __name__ == '__main__': 485 | # logging.basicConfig(filename='/storage/emulated/0/example.txt',level=logging.INFO) 486 | app = QApplication(sys.argv) 487 | w = MainWindow() 488 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /pyqt5_androiddeploy_instruct.rst: -------------------------------------------------------------------------------- 1 | **Project** **Title** 2 | 3 | PyqtDeploy 4 | 5 | Getting Started: 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 8 | 9 | **Prerequisites**: 10 | 11 | 1. There will PyQt5, pyqtdeploy, Android Studio, Apache, NDK, SDK, python, Java and QTCreator installed 12 | 13 | **PyQt5**: pip3 install PyQt5 14 | 15 | **pyqtdeploy**: pip3 install pyqtdeploy 16 | 17 | **python**: python version 3.6.4 18 | 19 | **Java**: sudo apt-get install openjdk-8-jdk 20 | 21 | **QTCreator**: QTCreator version 5.10.0 22 | 23 | - wget http://download.qt.io/official_releases/qt/5.10/5.10.0/qt-opensource-linux-x64-5.10.0.run 24 | 25 | - chmod +x qt-opensource-linux-x64-5.10.0.run 26 | 27 | - ./qt-opensource-linux-x64-5.7.0.run 28 | 29 | **Apache**: apache verion apache-ant-1.10.5 30 | 31 | - wget http://mirror.its.dal.ca/apache//ant/binaries/apache-ant-1.10.5-bin.tar.gz 32 | 33 | - sudo tar -xf apache-ant-1.10.5-bin.tar.gz -C /usr/local 34 | 35 | - sudo ln -s /usr/local/apache-ant-1.10.3/ /usr/local/ant 36 | 37 | **Android** **Studio**: You can install Android Studio from: "https://developer.android.com/studio/" 38 | 39 | **NDK**: NDK version android-ndk-r10e 40 | 41 | **SDK**: You can configure the SDK from Android Studio 42 | 43 | 2. Configure the bashfile for export all relavent path. Like: 44 | 45 | export ANDROID_HOME=/opt/android/sdk 46 | 47 | export PATH=$PATH:/opt/android/sdk/platforms 48 | 49 | export PATH=$PATH:/opt/android/sdk/platforms-tools 50 | 51 | export PATH=$PATH:/opt/android/sdk/build-tools 52 | 53 | export PATH=$PATH:/opt/android/sdk/tools 54 | 55 | export PATH=$PATH:/home/cis/Documents/android-ndk-r10e 56 | 57 | export ANT_HOME=/home/cis/apache-ant-1.10.5 58 | 59 | export PATH=$PATH:/home/cis/apache-ant-1.10.5/bin 60 | 61 | export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 62 | 63 | export PATH=$PATH:$ANDROID_HOME/tools:apache-ant-1.10.5/bin/ant/bin:$JAVA_HOME/bin 64 | 65 | export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee' 66 | 67 | export ANDROID_NDK_ROOT=/home/cis/Documents/android-ndk-r10e 68 | 69 | export ANDROID_NDK_PLATFORM="android-21" 70 | 71 | export ANDROID_TOOLCHAIN=/home/cis/Documents/android-ndk-r10e/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/ 72 | 73 | export ANDROID_NDK_TOOLCHAIN_VERSION='4.9' 74 | 75 | export SYSROOT=/home/cis/Qt5.10.0/5.10.0/android_armv7 76 | 77 | 78 | **Installing** 79 | 80 | 1. You need to install the pyqtdeploy tar package from the below link: 81 | 82 | "https://pypi.org/project/pyqtdeploy/#files" and untar the package. 83 | 84 | 2. Then you need to install the below tar files and need to place these tar files inside the "demo" folder of pyqtdeploy (which you have untar above) 85 | 1. openssl-1.0.2n.tar.gz 86 | 2. PyQt3D_gpl-5.10.1.tar.gz 87 | 3. PyQt5_gpl-5.10.1.tar.gz 88 | 4. PyQtChart_gpl-5.10.1.tar.gz 89 | 5. PyQtDataVisualization_gpl-5.10.1.tar.gz 90 | 6. PyQtPurchasing_gpl-5.10.1.tar.gz 91 | 7. Python-3.6.4.tar.xz 92 | 8. QScintilla_gpl-2.10.7.tar.xz 93 | 9. sip-4.19.8.tar.gz 94 | 95 | most of the tar files will get from : "https://sourceforge.net/projects/pyqt/files/" 96 | 97 | 3. Then you need to also place "android_armv7" this folder inside the "demo" folder of pyqtdeploy.You will get android_armv7 from QTCreator folder which will install earlier(Qt5.10.0/5.10.0/android_armv7) 98 | 99 | 4. You also need to change the pyqt-demo.pdy file. To make changes in it run command "pyqtdeploy pyqt-demo.pdy" this will open a window in which we need to change the python to which we are using and in Standard Library section you need to check the xmlrpc, json, base64 and ssl and then save it. 100 | 101 | 5. Then you need to copy the SDK folder which is configured earlier to the root of system in opt folder the folder.Structure will be /opt/android/sdk/ and you also need to give permission to android folder recursively, so that all inner folders get permission as well. 102 | 103 | **Deployment** 104 | 105 | After all the installation and setup, need to run the command where the build-demo.py file is kept. 106 | 107 | The command is: 108 | 109 | python3 build-demo.py --target android-32 --installed-qt-dir (/home/cis/Qt5.10.0/5.10.0) 110 | -------------------------------------------------------------------------------- /pyqtclient.py: -------------------------------------------------------------------------------- 1 | import xmlrpc.client 2 | import sys 3 | import base64 4 | from PyQt5 import QtCore, QtWidgets, Qt 5 | from PyQt5.QtWidgets import QApplication, QPushButton,QFormLayout,QMainWindow, QWidget, QLabel, QLineEdit, QPlainTextEdit 6 | from PyQt5.QtGui import QRegExpValidator 7 | 8 | class Addition(QMainWindow): 9 | def __init__(self): 10 | QMainWindow.__init__(self) 11 | 12 | self.widget = QWidget() 13 | fbox = QFormLayout() 14 | 15 | l1 = QLabel("TO: ") 16 | self.add1 = QLineEdit() 17 | self.add1.setText("BM-2cWyUfBdY2FbgyuCb7abFZ49JYxSzUhNFe") 18 | self.add1.setReadOnly(True) 19 | fbox.addRow(l1,self.add1) 20 | 21 | l2 = QLabel("FROM: ") 22 | self.add2 = QLineEdit() 23 | self.add2.setText("BM-2cV5v5bqzVznBXk3fsLaAfPkCnC42Dhwc4") 24 | self.add2.setReadOnly(True) 25 | fbox.addRow(l2,self.add2) 26 | 27 | l4 = QLabel("SUBJECT: ") 28 | self.add4 = QLineEdit() 29 | fbox.addRow(l4,self.add4) 30 | 31 | l3 = QLabel("MESSAGE: ") 32 | self.add3 = QPlainTextEdit() 33 | fbox.addRow(l3,self.add3) 34 | 35 | b = QPushButton() 36 | b.setText("SEND") 37 | b.clicked.connect(self.addition) 38 | fbox.addRow(b) 39 | 40 | 41 | self.widget.setLayout(fbox) 42 | self.widget.show() 43 | self.setCentralWidget(self.widget) 44 | self.setWindowTitle("SEND") 45 | 46 | def addition(self): 47 | fromAddress = self.add2.text() 48 | toAddress = self.add1.text() 49 | subject = base64.b64encode(bytes(self.add4.text(), 'utf-8')).decode("utf-8") 50 | message = base64.b64encode(bytes(self.add3.toPlainText(), 'utf-8')).decode("utf-8") 51 | api = xmlrpc.client.ServerProxy("http://username:password@127.0.0.1:8442/") 52 | sums = str(api.sendMessage(toAddress, fromAddress, subject, message)) 53 | 54 | if __name__ == "__main__": 55 | app = QApplication(sys.argv) 56 | apps = Addition() 57 | apps.show() 58 | sys.exit(app.exec_()) --------------------------------------------------------------------------------