Skip to content

Commit

Permalink
Added the startup widget and project dialog (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
RabiyaF authored Sep 3, 2024
1 parent fc1e6ae commit 998ff0a
Show file tree
Hide file tree
Showing 24 changed files with 550 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ venv.bak/
# IDE config folders
.idea
.vscode

# OS specific files
**/.DS_Store

# log files
**/logs/
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ To install pytest use

Run the tests and generate a coverage report with

pytest tests --cov=RAT
pytest tests --cov=rascal2

The coverage report can be saved to the directory htmlcov by running the tests with

pytest tests --cov-report html --cov=RAT
pytest tests --cov-report html --cov=rascal2

For information on other coverage report formats, see https://pytest-cov.readthedocs.io/en/latest/reporting.html

Expand Down
178 changes: 178 additions & 0 deletions rascal2/dialogs/project_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import os

from PyQt6 import QtCore, QtGui, QtWidgets

from rascal2.config import path_for


class ProjectDialog(QtWidgets.QDialog):
"""
The Project dialog
"""

_button_style = """background-color: {};
color: #F2F1E8;
padding-top: 0.3em;
padding-left: 1em;
padding-right: 1em;
padding-bottom: 0.3em;
font-weight: bold;
border-radius: 0.5em"""
_label_style = "font-weight: bold"
_error_style = "color: #E34234"
_line_edit_error_style = "border: 1px solid #E34234"

def __init__(self, parent):
"""
Initialize dialog.
Parameters
----------
parent: MainWindowView
An instance of the MainWindowView
"""
super().__init__(parent)

self.setModal(True)
self.setMinimumWidth(700)

self.folder_path = ""

self.create_buttons()
self.create_form()
self.add_widgets_to_layout()

def add_widgets_to_layout(self) -> None:
"""
Add widgets to the layout.
"""
self.main_layout = QtWidgets.QVBoxLayout()

self.main_layout.setSpacing(20)

# Add project name widgets
layout = QtWidgets.QGridLayout()
layout.setVerticalSpacing(2)
layout.addWidget(self.project_name_label, 0, 0, 1, 1)
layout.addWidget(self.project_name, 0, 1, 1, 5)
layout.addWidget(self.project_name_error, 1, 1, 1, 5)
self.main_layout.addLayout(layout)

# Add project folder widgets
layout = QtWidgets.QGridLayout()
layout.setVerticalSpacing(2)
layout.addWidget(self.project_folder_label, 0, 0, 1, 1)
layout.addWidget(self.project_folder, 0, 1, 1, 4)
layout.addWidget(self.browse_button, 0, 5, 1, 1)
layout.addWidget(self.project_folder_error, 1, 1, 1, 4)
self.main_layout.addLayout(layout)

# Add the create and cancel buttons
layout = QtWidgets.QHBoxLayout()
layout.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
layout.addWidget(self.create_button)
layout.addWidget(self.cancel_button)
self.main_layout.addStretch(1)
self.main_layout.addLayout(layout)

self.setLayout(self.main_layout)

def create_form(self) -> None:
"""
Create form widgets.
"""
# Project name widgets
self.project_name_label = QtWidgets.QLabel("Project Name:", self)
self.project_name_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
self.project_name_label.setStyleSheet(self._label_style)

self.project_name = QtWidgets.QLineEdit(self)
self.project_name.setPlaceholderText("Enter project name")

self.project_name_error = QtWidgets.QLabel("Project name needs to be specified.", self)
self.project_name_error.setStyleSheet(self._error_style)
self.project_name_error.hide()

# Project folder widgets
self.project_folder_label = QtWidgets.QLabel("Project Folder:", self)
self.project_folder_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
self.project_folder_label.setStyleSheet(self._label_style)

self.project_folder = QtWidgets.QLineEdit(self)
self.project_folder.setReadOnly(True)
self.project_folder.setPlaceholderText("Select project folder")

self.project_folder_error = QtWidgets.QLabel("An empty project folder needs to be selected.", self)
self.project_folder_error.setStyleSheet(self._error_style)
self.project_folder_error.hide()

def create_buttons(self) -> None:
"""
Create buttons.
"""
self.browse_button = QtWidgets.QPushButton(" Browse", self)
self.browse_button.setIcon(QtGui.QIcon(path_for("browse-light.png")))
self.browse_button.clicked.connect(self.open_folder_selector)
self.browse_button.setStyleSheet(self._button_style.format("#403F3F"))

self.create_button = QtWidgets.QPushButton(" Create", self)
self.create_button.setIcon(QtGui.QIcon(path_for("create.png")))
self.create_button.clicked.connect(self.create_project)
self.create_button.setStyleSheet(self._button_style.format("#0D69BB"))

self.cancel_button = QtWidgets.QPushButton(" Cancel", self)
self.cancel_button.setIcon(QtGui.QIcon(path_for("cancel.png")))
self.cancel_button.clicked.connect(self.reject)
self.cancel_button.setStyleSheet(self._button_style.format("#E34234"))

def open_folder_selector(self) -> None:
"""
Open folder selector.
"""
self.folder_path = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Folder")
if self.folder_path:
self.verify_folder()

def verify_folder(self) -> None:
"""
Verify the project folder.
"""
error = False
if self.folder_path:
files_in_folder = [file for file in os.listdir(self.folder_path) if not file.startswith(".")]
if files_in_folder:
error = True
elif self.project_folder.text() == "":
error = True

if error:
self.project_folder.setStyleSheet(self._line_edit_error_style)
self.project_folder_error.show()
self.project_folder.setText("")
else:
self.project_folder.setStyleSheet("")
self.project_folder_error.hide()
self.project_folder.setText(self.folder_path)

def verify_name(self) -> None:
"""
Verify the project name.
"""
if self.project_name.text() == "":
self.project_name.setStyleSheet(self._line_edit_error_style)
self.project_name_error.show()
else:
self.project_name.setStyleSheet("")
self.project_name_error.hide()

def create_project(self) -> None:
"""
Create project if inputs verified.
"""
self.verify_name()
self.verify_folder()
if self.project_name_error.isHidden() and self.project_folder_error.isHidden():
self.parent().presenter.createProject(self.project_name.text(), self.project_folder.text())
if not self.parent().toolbar.isEnabled():
self.parent().toolbar.setEnabled(True)
self.accept()
Binary file added rascal2/static/images/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/browse-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/browse-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/cancel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/export-plots.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed rascal2/static/images/file.png
Binary file not shown.
Binary file added rascal2/static/images/footer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/import-r1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified rascal2/static/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/new-project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/redo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/save-project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/tile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added rascal2/static/images/undo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions rascal2/ui/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class MainWindowPresenter:
def __init__(self, view):
self.view = view
self.model = MainWindowModel()
self.title = self.view.windowTitle()

def createProject(self, name: str, save_path: str):
"""Creates a new RAT project and controls object then initialise UI.
Expand All @@ -26,6 +27,7 @@ def createProject(self, name: str, save_path: str):
"""

self.model.createProject(name, save_path)
self.view.setWindowTitle(self.title + " - " + name)
# TODO if the view's central widget is the startup one then setup MDI else reset the widgets.
self.view.init_settings_and_log(save_path)
self.view.setupMDI()
87 changes: 78 additions & 9 deletions rascal2/ui/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from PyQt6 import QtCore, QtGui, QtWidgets

from rascal2.config import path_for, setup_logging, setup_settings
from rascal2.dialogs.project_dialog import ProjectDialog
from rascal2.widgets.startup_widget import StartUpWidget

from .presenter import MainWindowPresenter

Expand All @@ -14,6 +16,8 @@ class MainWindowView(QtWidgets.QMainWindow):

def __init__(self):
super().__init__()
self.setWindowTitle(MAIN_WINDOW_TITLE)

self.presenter = MainWindowPresenter(self)
window_icon = QtGui.QIcon(path_for("logo.png"))

Expand All @@ -35,17 +39,62 @@ def __init__(self):
self.createToolBar()
self.createStatusBar()

self.setWindowTitle(MAIN_WINDOW_TITLE)
self.setMinimumSize(1024, 900)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)

self.startup_dlg, self.project_dlg = StartUpWidget(self), ProjectDialog(self)

self.setCentralWidget(self.startup_dlg)

def showProjectDialog(self):
"""Shows the project dialog to create a new project"""
if self.startup_dlg.isVisible():
self.startup_dlg.hide()
self.project_dlg = ProjectDialog(self)
if (
self.project_dlg.exec() != QtWidgets.QDialog.DialogCode.Accepted
and self.centralWidget() is self.startup_dlg
):
self.startup_dlg.show()

def createActions(self):
"""Creates the menu and toolbar actions"""

self.new_project_action = QtGui.QAction("&New", self)
self.new_project_action.setStatusTip("Create a new project")
self.new_project_action.setIcon(QtGui.QIcon(path_for("file.png")))
self.new_project_action.setIcon(QtGui.QIcon(path_for("new-project.png")))
self.new_project_action.triggered.connect(self.showProjectDialog)
self.new_project_action.setShortcut(QtGui.QKeySequence.StandardKey.New)

self.open_project_action = QtGui.QAction("&Open", self)
self.open_project_action.setStatusTip("Open an existing project")
self.open_project_action.setIcon(QtGui.QIcon(path_for("browse-dark.png")))
self.open_project_action.setShortcut(QtGui.QKeySequence.StandardKey.Open)

self.save_project_action = QtGui.QAction("&Save", self)
self.save_project_action.setStatusTip("Save project")
self.save_project_action.setIcon(QtGui.QIcon(path_for("save-project.png")))
self.save_project_action.setShortcut(QtGui.QKeySequence.StandardKey.Save)

self.undo_action = QtGui.QAction("&Undo", self)
self.undo_action.setStatusTip("Undo")
self.undo_action.setIcon(QtGui.QIcon(path_for("undo.png")))
self.undo_action.setShortcut(QtGui.QKeySequence.StandardKey.Undo)

self.redo_action = QtGui.QAction("&Redo", self)
self.redo_action.setStatusTip("Redo")
self.redo_action.setIcon(QtGui.QIcon(path_for("redo.png")))
self.redo_action.setShortcut(QtGui.QKeySequence.StandardKey.Redo)

self.export_plots_action = QtGui.QAction("Export", self)
self.export_plots_action.setStatusTip("Export Plots")
self.export_plots_action.setIcon(QtGui.QIcon(path_for("export-plots.png")))

self.open_help_action = QtGui.QAction("&Help", self)
self.open_help_action.setStatusTip("Open Documentation")
self.open_help_action.setIcon(QtGui.QIcon(path_for("help.png")))
self.open_help_action.triggered.connect(self.openDocs)

self.exit_action = QtGui.QAction("E&xit", self)
self.exit_action.setStatusTip(f"Quit {MAIN_WINDOW_TITLE}")
self.exit_action.setShortcut(QtGui.QKeySequence.StandardKey.Quit)
Expand All @@ -54,6 +103,7 @@ def createActions(self):
# Window menu actions
self.tile_windows_action = QtGui.QAction("Tile Windows", self)
self.tile_windows_action.setStatusTip("Arrange windows in the default grid")
self.tile_windows_action.setIcon(QtGui.QIcon(path_for("tile.png")))
self.tile_windows_action.triggered.connect(self.mdi.tileSubWindows)

def createMenus(self):
Expand All @@ -67,18 +117,36 @@ def createMenus(self):
file_menu.addAction(self.exit_action)

# edit_menu = main_menu.addMenu("&Edit")
# tools_menu = main_menu.addMenu("&Tools")

tools_menu = main_menu.addMenu("&Tools")
tools_menu.addAction(self.undo_action)
tools_menu.addAction(self.redo_action)

windows_menu = main_menu.addMenu("&Windows")
windows_menu.addAction(self.tile_windows_action)
# help_menu = main_menu.addMenu("&Help")

help_menu = main_menu.addMenu("&Help")
help_menu.addAction(self.open_help_action)

def openDocs(self):
"""Opens the documentation"""
url = QtCore.QUrl("https://rascalsoftware.github.io/RAT-Docs/dev/index.html")
QtGui.QDesktopServices.openUrl(url)

def createToolBar(self):
"""Creates the toolbar"""
toolbar = self.addToolBar("ToolBar")
toolbar.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.PreventContextMenu)
toolbar.setMovable(False)

toolbar.addAction(self.new_project_action)
self.toolbar = self.addToolBar("ToolBar")
self.toolbar.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.PreventContextMenu)
self.toolbar.setMovable(False)
self.toolbar.setEnabled(False)

self.toolbar.addAction(self.new_project_action)
self.toolbar.addAction(self.open_project_action)
self.toolbar.addAction(self.save_project_action)
self.toolbar.addAction(self.undo_action)
self.toolbar.addAction(self.redo_action)
self.toolbar.addAction(self.export_plots_action)
self.toolbar.addAction(self.open_help_action)

def createStatusBar(self):
"""Creates the status bar"""
Expand All @@ -102,6 +170,7 @@ def setupMDI(self):
window.setWindowTitle(title)
# TODO implement user save for layouts, this should default to use saved layout and fallback to tile
self.mdi.tileSubWindows()
self.startup_dlg = self.takeCentralWidget()
self.setCentralWidget(self.mdi)

def init_settings_and_log(self, save_path: str):
Expand Down
Loading

0 comments on commit 998ff0a

Please sign in to comment.