Skip to content

Commit

Permalink
Merge pull request #29 from ntoll/ui_core
Browse files Browse the repository at this point in the history
GUI scaffolding and core functions
  • Loading branch information
redshiftzero authored Oct 10, 2018
2 parents 86e8ad2 + b139b71 commit f12019a
Show file tree
Hide file tree
Showing 28 changed files with 1,845 additions and 72 deletions.
6 changes: 1 addition & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ jobs:
command: |
pipenv install --dev
export PYTHONPATH=$PYTHONPATH:. # so alembic can get to Base metadata
pipenv run pytest -v
- run:
name: Run flake8
command: pipenv run flake8
pipenv run make check
- run:
name: Check Python dependencies for known vulnerabilities
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ clean:
find . | grep -E "(__pycache__)" | xargs rm -rf

test: clean
pytest
xvfb-run python -m pytest

pyflakes:
find . \( -name _build -o -name var -o -path ./docs -o -path \) -type d -prune -o -name '*.py' -print0 | $(XARGS) pyflakes
Expand All @@ -33,6 +33,6 @@ pycodestyle:
find . \( -name _build -o -name var \) -type d -prune -o -name '*.py' -print0 | $(XARGS) -n 1 pycodestyle --repeat --exclude=build/*,docs/*,.vscode/* --ignore=E731,E402,W504

coverage: clean
pytest --cov-config .coveragerc --cov-report term-missing --cov=securedrop_client tests/
xvfb-run python -m pytest --cov-config .coveragerc --cov-report term-missing --cov=securedrop_client tests/

check: clean pycodestyle pyflakes coverage
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ SQLALchemy = "*"
alembic = "*"
securedrop-sdk = {git = "https://github.com/freedomofpress/securedrop-sdk.git"}
"pathlib2" = "*"
"pyqt5" = "==5.10.1"
arrow = "*"

[dev-packages]
pytest = "*"
Expand Down
68 changes: 48 additions & 20 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env python3
from securedrop_client.app import run


if __name__ == '__main__':
run()
21 changes: 21 additions & 0 deletions securedrop_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
import gettext
import locale
import os


# Configure locale and language.
# Define where the translation assets are to be found.
localedir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'locale'))
try:
# Use the operating system's locale.
current_locale, encoding = locale.getdefaultlocale()
# Get the language code.
language_code = current_locale[:2]
except (TypeError, ValueError):
language_code = 'en'
# DEBUG/TRANSLATE: override the language code here (e.g. to Chinese).
# language_code = 'zh'
gettext.translation('securedrop_client', localedir=localedir,
languages=[language_code], fallback=True).install()


__version__ = '0.0.1-alpha.1'
28 changes: 25 additions & 3 deletions securedrop_client/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@
import pathlib
import os
import sys
from sqlalchemy.orm import sessionmaker
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from logging.handlers import TimedRotatingFileHandler
from securedrop_client import __version__
from securedrop_client.logic import Client
from securedrop_client.gui.main import Window
from securedrop_client.resources import load_icon, load_css
from securedrop_client.models import engine


LOG_DIR = os.path.join(str(pathlib.Path.home()), '.securedrop_client')
Expand Down Expand Up @@ -69,11 +76,26 @@ def run():
run the application. Specific tasks include:
- set up logging.
ToDo:
- create an application object.
- create a window for the app.
- create an API connection to the SecureDrop proxy.
- create a SqlAlchemy session to local storage.
- configure the client (logic) object.
- ensure the application is setup in the default safe starting state.
"""
configure_logging()
logging.info('Starting SecureDrop Client {}'.format(__version__))

app = QApplication(sys.argv)
app.setApplicationName('SecureDrop Client')
app.setDesktopFileName('org.freedomofthepress.securedrop.client')
app.setApplicationVersion(__version__)
app.setAttribute(Qt.AA_UseHighDpiPixmaps)
gui = Window()
app.setWindowIcon(load_icon(gui.icon))
app.setStyleSheet(load_css('sdclient.css'))
Session = sessionmaker(bind=engine)
session = Session()
client = Client("http://localhost:8081/", gui, session)
client.setup()
sys.exit(app.exec_())
Empty file.
117 changes: 117 additions & 0 deletions securedrop_client/gui/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Contains the core UI class for the application. All interactions with the UI
go through an instance of this class.
Copyright (C) 2018 The Freedom of the Press Foundation.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import logging
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QDesktopWidget
from PyQt5.QtCore import Qt
from securedrop_client import __version__
from securedrop_client.gui.widgets import ToolBar, MainView, LoginView
from securedrop_client.resources import load_icon


logger = logging.getLogger(__name__)


class Window(QMainWindow):
"""
Represents the application's main window that will contain the UI widgets.
All interactions with the UI go through the object created by this class.
"""

icon = 'icon.png'

def __init__(self):
"""
Create the default start state. The window contains a root widget into
which is placed:
* A status bar widget at the top, containing curent user / status
information.
* A main-view widget, itself containing a list view for sources and a
place for details / message contents / forms.
"""
super().__init__()
self.setWindowTitle(_("SecureDrop Client {}").format(__version__))
self.setWindowIcon(load_icon(self.icon))
self.widget = QWidget()
widget_layout = QVBoxLayout()
self.widget.setLayout(widget_layout)
self.tool_bar = ToolBar(self.widget)
self.main_view = MainView(self.widget)
widget_layout.addWidget(self.tool_bar, 1)
widget_layout.addWidget(self.main_view, 6)
self.setCentralWidget(self.widget)
self.show()
self.autosize_window()

def setup(self, controller):
"""
Create references to the controller logic and instantiate the various
views used in the UI.
"""
self.controller = controller # Reference the Client logic instance.
self.tool_bar.setup(self, controller)
self.login_view = LoginView(self, self.controller)

def autosize_window(self):
"""
Ensure the application window takes up 100% of the available screen
(i.e. the whole of the virtualised desktop in Qubes dom)
"""
screen = QDesktopWidget().screenGeometry()
self.resize(screen.width(), screen.height())

def show_login(self, error=None):
"""
Show the login form. If an error message is passed in, the login
form will display this too.
"""
self.login_view.reset()
if error:
self.login_view.error(error)
self.main_view.update_view(self.login_view)

def show_sources(self, sources):
"""
Update the left hand sources list in the UI with the passed in list of
sources.
"""
self.main_view.source_list.update(sources)

def show_sync(self, updated_on):
"""
Display a message indicating the data-sync state.
"""
if updated_on:
self.main_view.status.setText('Last Sync: ' +
updated_on.humanize())
else:
self.main_view.status.setText(_('Waiting to Synchronize'))

def set_logged_in_as(self, username):
"""
Update the UI to show user logged in with username.
"""
self.tool_bar.set_logged_in_as(username)

def logout(self):
"""
Update the UI to show the user is logged out.
"""
self.tool_bar.set_logged_out()
Loading

0 comments on commit f12019a

Please sign in to comment.