Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cxrodgers dev terminal winsize #70

Merged
merged 8 commits into from Feb 10, 2021
Merged
210 changes: 83 additions & 127 deletions autopilot/core/terminal.py
@@ -1,16 +1,15 @@
"""Methods for running the Terminal GUI"""

import argparse
import json
import sys
import os

import datetime
import logging
import threading
from collections import OrderedDict as odict
import numpy as np

from PySide2 import QtCore, QtGui, QtSvg, QtWidgets

from autopilot import prefs
from autopilot.core import styles

Expand All @@ -35,16 +34,14 @@
# init prefs for module access
prefs.init(prefs_file)



from autopilot.core.subject import Subject
from autopilot.core.plots import Plot_Widget
from autopilot.core.networking import Terminal_Station, Net_Node
from autopilot.core.utils import InvokeEvent, Invoker, get_invoker
from autopilot.core.gui import Control_Panel, Protocol_Wizard, Weights, Reassign, Calibrate_Water, Bandwidth_Test
from autopilot.core.loggers import init_logger


# Try to import viz, but continue if that doesn't work
IMPORTED_VIZ = False
VIZ_ERROR = None
try:
Expand Down Expand Up @@ -123,6 +120,12 @@ def __init__(self):
# store instance
globals()['_TERMINAL'] = self

# Load settings
# These are stored in ~/.config/Autopilot/Terminal.conf
# Currently, the only setting is "geometry", but loading here
# in case we start to use other ones in the future
self.settings = QtCore.QSettings("Autopilot", "Terminal")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. let's move it to be in the autopilot directory so it's all in one place? looks like QSettings supports being constructed with a filename, i'll add a pref for that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add description to docstring too


# networking
self.node = None
self.networking = None
Expand Down Expand Up @@ -192,9 +195,6 @@ def __init__(self):
#self.heartbeat(once=True)
self.logger.info('Terminal Initialized')




Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty for cleaning up my mess ;)

def initUI(self):
"""
Initializes graphical elements of Terminal.
Expand All @@ -205,40 +205,42 @@ def initUI(self):
* :class:`.gui.Control_Panel`
* :class:`.plots.Plot_Widget`
"""


# set central widget
# Set central widget
self.widget = QtWidgets.QWidget()
self.setCentralWidget(self.widget)



# Start GUI
# Set the layout
self.layout = QtWidgets.QGridLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0,0,0,0)
self.widget.setLayout(self.layout)

# Set title
self.setWindowTitle('Terminal')
#self.menuBar().setFixedHeight(40)

# Main panel layout
#self.panel_layout.setContentsMargins(0,0,0,0)

# Init toolbar
# File menu
# make menu take up 1/10 of the screen
winsize = app.desktop().availableGeometry()

# This is the pixel resolution of the entire screen
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appreciate this update and commenting!!!!

screensize = app.primaryScreen().size()

# This is the available geometry of the primary screen, excluding
# window manager reserved areas such as task bars and system menus.
primary_display = app.primaryScreen().availableGeometry()


## Initalize the menuBar
# Linux: Set the menuBar to a fixed height
# Darwin: Don't worry about menuBar
if sys.platform == 'darwin':
bar_height = 0
else:
bar_height = (winsize.height()/30)+5
bar_height = (primary_display.height()/30)+5
self.menuBar().setFixedHeight(bar_height)


# Create a File menu
self.file_menu = self.menuBar().addMenu("&File")
self.file_menu.setObjectName("file")

# Add "New Pilot" and "New Protocol" actions to File menu
new_pilot_act = QtWidgets.QAction("New &Pilot", self, triggered=self.new_pilot)
new_prot_act = QtWidgets.QAction("New Pro&tocol", self, triggered=self.new_protocol)
#batch_create_subjects = QtWidgets.QAction("Batch &Create subjects", self, triggered=self.batch_subjects)
Expand All @@ -247,8 +249,10 @@ def initUI(self):
self.file_menu.addAction(new_prot_act)
#self.file_menu.addAction(batch_create_subjects)

# Tools menu
# Create a Tools menu
self.tool_menu = self.menuBar().addMenu("&Tools")

# Add actions to Tools menu
subject_weights_act = QtWidgets.QAction("View Subject &Weights", self, triggered=self.subject_weights)
update_protocol_act = QtWidgets.QAction("Update Protocols", self, triggered=self.update_protocols)
reassign_act = QtWidgets.QAction("Batch Reassign Protocols", self, triggered=self.reassign_protocols)
Expand All @@ -258,12 +262,12 @@ def initUI(self):
self.tool_menu.addAction(reassign_act)
self.tool_menu.addAction(calibrate_act)

# Plots menu
# Create a Plots menu and add Psychometric Curve action
self.plots_menu = self.menuBar().addMenu("&Plots")
psychometric = QtGui.QAction("Psychometric Curve", self, triggered=self.plot_psychometric)
self.plots_menu.addAction(psychometric)

# Tests menu
# Create a Tests menu and add a Test Bandwidth action
self.tests_menu = self.menuBar().addMenu("Test&s")
bandwidth_test_act = QtWidgets.QAction("Test Bandwidth", self, triggered=self.test_bandwidth)
self.tests_menu.addAction(bandwidth_test_act)
Expand All @@ -279,89 +283,69 @@ def initUI(self):
self.data_panel = Plot_Widget()
self.data_panel.init_plots(self.pilots.keys())



# Logo goes up top
# https://stackoverflow.com/questions/25671275/pyside-how-to-set-an-svg-icon-in-qtreewidgets-item-and-change-the-size-of-the

#
# pixmap_path = os.path.join(os.path.dirname(prefs.get('AUTOPILOT_ROOT')), 'graphics', 'autopilot_logo_small.svg')
# #svg_renderer = QtSvg.QSvgRenderer(pixmap_path)
# #image = QtWidgets.QImage()
# #self.logo = QtSvg.QSvgWidget()
#
#
# # set size, preserving aspect ratio
# logo_height = round(44.0*((bar_height-5)/44.0))
# logo_width = round(139*((bar_height-5)/44.0))
#
# svg_renderer = QtSvg.QSvgRenderer(pixmap_path)
# image = QtGui.QImage(logo_width, logo_height, QtGui.QImage.Format_ARGB32)
# # Set the ARGB to 0 to prevent rendering artifacts
# image.fill(0x00000000)
# svg_renderer.render(QtGui.QPainter(image))
# pixmap = QtGui.QPixmap.fromImage(image)
# self.logo = QtWidgets.QLabel()
# self.logo.setPixmap(pixmap)

# Set logo to corner widget
if sys.platform != 'darwin':
self.menuBar().setCornerWidget(self.logo, QtCore.Qt.TopRightCorner)
self.menuBar().adjustSize()

#self.logo.load(pixmap_path)
# Combine all in main layout
# Add Control Panel and Data Panel to main layout
#self.layout.addWidget(self.logo, 0,0,1,2)
self.layout.addWidget(self.control_panel, 0,0,1,1)
self.layout.addWidget(self.data_panel, 0,1,1,1)
self.layout.setColumnStretch(0, 1)
self.layout.setColumnStretch(1, 3)

# Set size of window to be fullscreen without maximization
# Until a better solution is found, if not set large enough, the pilot tabs will
# expand into infinity. See the Expandable_Tabs class
#pdb.set_trace()
screensize = app.desktop().screenGeometry()
winsize = app.desktop().availableGeometry()

# want to subtract bounding title box, our title bar, and logo height.
# our y offset will be the size of the bounding title box

# Then our tilebar
# multiply by three to get the inner (file, etc.) bar, the top bar (min, maximize, etc)
# and then the very top system tray bar in ubuntu
#titleBarHeight = self.style().pixelMetric(QtWidgets.QStyle.PM_TitleBarHeight,
# QtWidgets.QStyleOptionTitleBar(), self) * 3
title_bar_height = screensize.height()-winsize.height()

#titleBarHeight = bar_height*2
# finally our logo
logo_height = bar_height



winheight = winsize.height() - title_bar_height - logo_height # also subtract logo height
winsize.setHeight(winheight)
self.max_height = winheight
self.setGeometry(winsize)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)

# Set heights on control panel and data panel


# move to primary display and show maximized
primary_display = app.desktop().availableGeometry(0)
self.move(primary_display.left(), primary_display.top())
# self.resize(primary_display.width(), primary_display.height())
#
self.control_panel.setMaximumHeight(winheight)
self.data_panel.setMaximumHeight(winheight)

## Set window size
# The window size behavior depends on TERMINAL_WINSIZE_BEHAVIOR pref
# If 'remember': restore to the geometry from the last close
# If 'maximum': restore to fill the entire screen
# If 'moderate': restore to a reasonable size of (1000, 400) pixels
terminal_winsize_behavior = prefs.get('TERMINAL_WINSIZE_BEHAVIOR')

# Set geometry according to pref
if terminal_winsize_behavior == 'maximum':
# Set geometry to available geometry
self.setGeometry(primary_display)

# Set SizePolicy to maximum
self.setSizePolicy(
QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)

# Move to top left corner of primary display
self.move(primary_display.left(), primary_display.top())

# Also set the maximum height of each panel
self.control_panel.setMaximumHeight(primary_display.height())
self.data_panel.setMaximumHeight(primary_display.height())

elif terminal_winsize_behavior == 'remember':
# Attempt to restore previous geometry
if self.settings.value("geometry") is None:
# It was never saved, for instance, this is the first time
# this app has been run
# So default to the moderate size
self.move(primary_display.left(), primary_display.top())
self.resize(1000, 400)
else:
# It was saved, so restore the last geometry
self.restoreGeometry(self.settings.value("geometry"))

else:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor style thing, make explicit and then fallback, will do. otherwise this looks good

# The moderate size
self.move(primary_display.left(), primary_display.top())
self.resize(1000, 400)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i take it these are empirical? that's fine, we mebs want to have some option for like "half screen," and i think i'll add the ability to set position explicitly too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, (1000, 400) is my favorite autopilot window size. I suppose we could call that "empirical". For the typical new user use-case, where they open it up and have either 0 or 1 Pis connected, something like this makes a bit more sense than full-screen (IMHO)



## Finalize some aesthetics
# set stylesheet for main window
self.setStyleSheet(styles.TERMINAL)

# set fonts to antialias
self.setFont(self.font().setStyleStrategy(QtGui.QFont.PreferAntialias))


## Show, and log that initialization is complete
self.show()
logging.info('UI Initialized')

Expand Down Expand Up @@ -399,7 +383,6 @@ def heartbeat(self, once=False):
self.heartbeat_timer.daemon = True
self.heartbeat_timer.start()


def toggle_start(self, starting, pilot, subject=None):
"""Start or Stop running the currently selected subject's task. Sends a
message containing the task information to the concerned pilot.
Expand Down Expand Up @@ -529,11 +512,6 @@ def l_state(self, value):
#self.control_panel.panels[value['pilot']].button.set_state(value['state'])
self.pilots[value['pilot']]['state'] = value['state']






def l_handshake(self, value):
"""
Pilot is sending its IP and state on startup.
Expand Down Expand Up @@ -721,7 +699,6 @@ def subject_protocols(self):

return subjects_protocols


def reassign_protocols(self):
"""
Batch reassign protocols and steps.
Expand Down Expand Up @@ -830,8 +807,6 @@ def plot_psychometric(self):
if psychometric_dialog.result() != 1:
return



chart = viz.plot_psychometric(psychometric_dialog.plot_params)

text, ok = QtGui.QInputDialog.getText(self, 'save plot?', 'what to call this thing')
Expand All @@ -841,27 +816,9 @@ def plot_psychometric(self):

#chart.serve()





#viz.plot_psychometric(self.subjects_protocols)
#result = psychometric_dialog.exec_()















def closeEvent(self, event):
"""
When Closing the Terminal Window, close any running subject objects,
Expand All @@ -871,6 +828,9 @@ def closeEvent(self, event):
to explicitly kill it.

"""
# Save the window geometry, to be optionally restored next time
self.settings.setValue("geometry", self.saveGeometry())

# TODO: Check if any subjects are currently running, pop dialog asking if we want to stop

# Close all subjects files
Expand All @@ -884,15 +844,11 @@ def closeEvent(self, event):

event.accept()

# Create the QApplication and run it
# Prefs were already loaded at the very top
if __name__ == "__main__":

#with open(prefs_file) as prefs_file_open:
# prefs = json.load(prefs_file_open)

app = QtWidgets.QApplication(sys.argv)
#app.setGraphicsSystem("opengl")
app.setStyle('GTK+') # Keeps some GTK errors at bay
ex = Terminal()
sys.exit(app.exec_())


10 changes: 9 additions & 1 deletion autopilot/prefs.py
Expand Up @@ -282,6 +282,15 @@ class Scopes(Enum):
"default": str(_basedir / "pilot_db.json"),
"scope": Scopes.TERMINAL
},
'TERMINAL_WINSIZE_BEHAVIOR': {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a multi-select box i think.

'type': 'str',
'text': (
'How the Terminal window is sized: '
'"maximum", "moderate", or "remember"'
),
'default': 'moderate',
"scope": Scopes.TERMINAL
},
'LINEAGE': {
'type': 'choice',
"text": "Are we a parent or a child?",
Expand Down Expand Up @@ -345,7 +354,6 @@ class Scopes(Enum):
'depends': 'AUDIOSERVER',
"scope": Scopes.AUDIO
},

})
"""
Ordered Dictionary containing default values for prefs.
Expand Down