Comment Banner/Header best practices/examples? - coding-style

Long ago, I got a quick glance at some code (PERL... urp) one of my managers was writing and was stunned by the banner/heading system he used in his code. I didn't get a chance to delve into his code in detail, but just from the banner comments on the screen I could easily tell what the code was meant to do, even from far away.
Unfortunately that was long ago and our conversation at the time did not lend itself to me saying, "Yeah, well forget about our dot-com startup going down the toilet, can I take notes on your coding style?"
Years later, I've not yet achieved a high-level comment style that has the clarity as the (probably now mythical) code I saw that day.
When I say, "banners", I'm referring to the high-level block divisions/headers many coders use to create higher-level devisions in their code. They're usually made up of simple ASCII dashes, slashes, equal signs, etc. In my current daily-use language, one code banner/header hierarchy might be:
# ========================================================
# = Header 1
# ========================================================
# --------------------------------------------------------
# - Header 2
#---------------------------------------------------------
# == Header 3 ============================================
# -- Header 4 --------------------------------------------
# Header 5
and all the usual variations.
Although my searches have turned up nothing noteworthy, surely somewhere on the web someone's attempted to collect examples of these and present them in a systematic manner?
Can someone point to banner comment style "systems" they've found useful? I'm not thinking of "Oh, I like the ones using asterisks", but more of an overall strategy of styles that makes high-level code construction quick and easy to understand as well as easy on the eyes? Picking a preferred system from examples would be easier than comparing descriptions, obviously.
Note: I'm not interested in the contents of the comments per se, but the "flair" used on the comments to provide a clear indications of overall code content and organization.

One of the best ways I have found of verifying the style of commenting is to use a code documentation tool such as doxygen, there is a list of others here, and then see what the output was like - the clearer the output the better the comments.
I would say that the single biggest points are consistency and a clear indication of ranking, followed by completeness and conciseness, i.e. Once you have looked at one you should know what the others will look like and how significant the one you are looking at is. This forces you to have a good design as without it you don't know how significant things are.
Next all the information you need should be present but it should be short enough to take in at a glance - however satisfying both these forces you to change your coding style so that objects/code is not too large, is well named, doesn't have too many parameters, etc., all the things that tools like lint try to teach us.
For python code the style summary at PEP-257 provides a lot of useful guidelines and some examples.
A quick look for some "Good" code on my machine turned up Andrea Gavana's Aquabutton.py I have included a section of the code below but you can see the full code here - I have to say that this was the first module of Andrea's that I opened, being early in the alphabet but I am sure any would have done.
# --------------------------------------------------------------------------------- #
# AQUABUTTON wxPython IMPLEMENTATION
#
# Andrea Gavana, # 07 October 2008
# Latest Revision: 24 Nov 2011, 22.00 GMT
#
#
# TODO List
#
# 1) Anything to do?
#
#
# For all kind of problems, requests of enhancements and bug reports, please
# write to me at:
#
# andrea.gavana#gmail.com
# andrea.gavana#maerskoil.com
#
# Or, obviously, to the wxPython mailing list!!!
#
#
# End Of Comments
# --------------------------------------------------------------------------------- #
"""
:class:`AquaButton` is another custom-drawn button class which *approximatively* mimics
the behaviour of Aqua buttons on the Mac.
Description
===========
:class:`AquaButton` is another custom-drawn button class which *approximatively* mimics
the behaviour of Aqua buttons on the Mac. At the moment this class supports:
* Bubble and shadow effects;
* Customizable background, foreground and hover colours;
* Rounded-corners buttons;
* Text-only or image+text buttons;
* Pulse effect on gaining focus.
And a lot more. Check the demo for an almost complete review of the functionalities.
Usage
=====
Sample usage::
import wx
import wx.lib.agw.aquabutton as AB
app = wx.App(0)
frame = wx.Frame(None, -1, "AquaButton Test")
mainPanel = wx.Panel(frame)
mainPanel.SetBackgroundColour(wx.WHITE)
# Initialize AquaButton 1 (with image)
bitmap = wx.Bitmap("my_button_bitmap.png", wx.BITMAP_TYPE_PNG)
btn1 = AB.AquaButton(mainPanel, -1, bitmap, "AquaButton")
# Initialize AquaButton 2 (no image)
btn2 = AB.AquaButton(mainPanel, -1, None, "Hello World!")
frame.Show()
app.MainLoop()
Supported Platforms
===================
AquaButton has been tested on the following platforms:
* Windows (Windows XP);
* Linux Ubuntu (10.10).
Window Styles
=============
`No particular window styles are available for this class.`
Events Processing
=================
This class processes the following events:
================= ==================================================
Event Name Description
================= ==================================================
``wx.EVT_BUTTON`` Process a `wxEVT_COMMAND_BUTTON_CLICKED` event, when the button is clicked.
================= ==================================================
License And Version
===================
:class:`AquaButton` control is distributed under the wxPython license.
Latest Revision: Andrea Gavana # 22 Nov 2011, 22.00 GMT
Version 0.4
"""
import wx
# Constants for the hovering and clicking effects
HOVER = 1
""" Indicates that the mouse is hovering over :class:`AquaButton` """
CLICK = 2
""" Indicates that :class:`AquaButton` has been clicked """
class AquaButtonEvent(wx.PyCommandEvent):
""" Event sent from the :class:`AquaButton` buttons when the button is activated. """
def __init__(self, eventType, eventId):
"""
Default class constructor.
:param integer `eventType`: the event type;
:param integer `eventId`: the event identifier.
"""
wx.PyCommandEvent.__init__(self, eventType, eventId)
self.isDown = False
self.theButton = None
def SetButtonObj(self, btn):
"""
Sets the event object for the event.
:param `btn`: the button object, an instance of :class:`AquaButton`.
"""
self.theButton = btn
snip
class AquaButton(wx.PyControl):
""" This is the main class implementation of :class:`AquaButton`. """
def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator,
name="aquabutton"):
"""
Default class constructor.
:param Window `parent`: parent window. Must not be ``None``;
:param integer `id`: window identifier. A value of -1 indicates a default value;
:param Bitmap `bitmap`: the button bitmap (if any);
:param string `label`: the button text label;
:param `pos`: the control position. A value of (-1, -1) indicates a default position,
chosen by either the windowing system or wxPython, depending on platform;
:type `pos`: tuple or :class:`Point`
:param `size`: the control size. A value of (-1, -1) indicates a default size,
chosen by either the windowing system or wxPython, depending on platform;
:type `size`: tuple or :class:`Size`
:param integer `style`: the button style (unused);
:param Validator `validator`: the validator associated to the button;
:param string `name`: the button name.
"""
wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter)
self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
self.Bind(wx.EVT_TIMER, self.OnPulseTimer)
if "__WXMSW__" in wx.PlatformInfo:
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
self._mouseAction = None
self.SetBitmapLabel(bitmap)
self._hasFocus = False
self._saveBitmap = True
self._storedBitmap = wx.NullBitmap
self._pulseOnFocus = False
self._gammaFactor = 1.0
self._gammaIncrement = 0.1
self._timer = wx.Timer(self, wx.ID_ANY)
self.SetLabel(label)
self.InheritAttributes()
self.SetInitialSize(size)
# The following defaults are better suited to draw the text outline
if "__WXMAC__" in wx.PlatformInfo:
self._backColour = wx.Colour(147, 202, 255)
self._hoverColour = self.LightColour(self._backColour, 30)
self._disableColour = self.LightColour(self._backColour, 70)
self._textColour = wx.BLACK
else:
self._backColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
self._hoverColour = self.LightColour(self._backColour, 30)
self._disableColour = self.LightColour(self._backColour, 70)
self._textColour = wx.WHITE
def SetBitmapLabel(self, bitmap):
"""
Sets the bitmap label for the button.
:param `bitmap`: the bitmap label to set, an instance of :class:`Bitmap`.
"""
self._bitmap = bitmap
self.Refresh()

Related

Some PyQt5 Checkboxes are unclickable after being moved

I'm creating a to-do list where a checkbox, textbox (QLineEdit), and QComboBox (to set Priority 1, 2, etc.) are added each time the "Add Task" button is clicked. When the checkbox for each corresponding task is clicked, the task would move to the bottom of the list of tasks. Conversely, if a priority is set, then the task is moved to the top of the list of tasks.
The problem is that in certain circumstances, when I click on the checkbox, nothing happens. The signal that identifies that the checkbox state has changed is never sent. It's almost like the checkbox isn't there, even though it (or at least, the image of it) is. This problem happens if none of the priorities are set with unchecking—checking any given task works, but certain tasks cannot be unchecked. Other tasks are able to be unchecked, and sometimes if I go back to the task that couldn't be unchecked after unchecking something else, it'll be checkable again. If I were to set some tasks to have a priority and then go and try to check the checkboxes, some checkboxes won't be able to be checked at all. Again, if I were to check some other task and then go back to the task that wasn't able to be checked, it might be able to be checked this time around.
I think the error might be coming from moving the checkboxes... for some reason the functionality behind some of the checkboxes is lost going through the code. One notable thing is tabbing through the items in the GUI. Once items are moved, hitting tab will go through the items in the same order that it was originally set up in (so the cursor will essentially jump around the textboxes instead of going in order from top to bottom). Not sure if that has anything to do with the error; it's just something I've noticed. I'm don't know if this is an error within PyQt5 itself, or if it's a fixable error within the code.
import sys
from PyQt5.QtWidgets import (QWidget, QComboBox,
QPushButton, QApplication, QLabel, QLineEdit, QTextEdit, QMainWindow, QAction, QShortcut, QCheckBox)
from PyQt5.QtGui import (QKeySequence)
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot, QObject
class App(QMainWindow):
def __init__(self):
super().__init__()
self.i = 40
self.j = 80
self.counter = 1
self.list_eTasks = []
self.initUI()
def initUI(self):
#sets up window
self.setWindowTitle('To-Do List')
self.setGeometry(300, 300, 800, 855)
#button
self.btn1 = QPushButton("Add Task", self)
self.btn1.clicked.connect(self.add_task_button_clicked)
#close the window via keyboard shortcuts
self.exit = QShortcut(QKeySequence("Ctrl+W" or "Ctrl+Q"), self)
self.exit.activated.connect(self.close)
self.show()
#pyqtSlot()
def add_task_button_clicked(self):
self.label = QLabel(self)
self.label.setText(str(self.counter))
self.label.move(5, self.i)
self.btn1.move(50, self.j)
self.textbox = QLineEdit(self)
self.textbox.resize(280, 40)
self.checkbox = QCheckBox(self)
self.checkbox.stateChanged.connect(self.click_box)
self.combobox = QComboBox(self)
self.combobox.addItem("No Priority")
self.combobox.addItem("Priority 1")
self.combobox.addItem("Priority 2")
self.combobox.addItem("Priority 3")
self.combobox.activated[str].connect(self.combobox_changed)
self.obj = eTask(self.checkbox, self.textbox, self.combobox)
self.list_eTasks.append(self.obj)
self.textbox.show()
self.label.show()
self.checkbox.show()
self.combobox.show()
self.i += 40
self.j += 40
self.counter += 1
self.sort_eTasks()
def click_box(self, state):
self.sort_eTasks()
def move_everything(self, new_list):
count = 0
for item in new_list:
y_value = ((40)*(count + 1))
item.check.move(20, y_value)
item.text.move(50, y_value)
item.combo.move(350, y_value)
count += 1
return
def combobox_changed(self, state):
#make new list to not include any already checked off items
for task in self.list_eTasks:
if task.combo.currentText() == "Priority 1":
task.priority = 1
elif task.combo.currentText() == "Priority 2":
task.priority = 2
elif task.combo.currentText() == "Priority 3":
task.priority = 3
else:
task.priority = 10
self.sort_eTasks()
def sort_eTasks(self):
self.list_eTasks.sort(key=lambda eTask: eTask.getRank())
self.move_everything(self.list_eTasks)
for task in self.list_eTasks:
print(str(task.priority) + " pos: " + str(task.check.pos()))
print("-----------------------------------")
class eTask:
check = ""
text = ""
combo = ""
priority = 10
def __init__(self, c, t, co):
self.check = c
self.text = t
self.combo = co
def setP(self, p):
self.priority = p
def getRank(self):
if self.check.isChecked():
return self.priority + 100
else:
return self.priority
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
There are various problems with your code, I'll try to address all of them.
The sorting mechanism is not good: it only happens when an item changes the priority (while it should also apply whenever the user clicks the checkbox)
Using low values like those results in unexpected behavior: if I have a priority 1 checked its priority is augmented by 10, so why should it go under a priority 2 or even 3?
The sorting mechanism should be centralized, instead of being split into more functions that don't do anything else: "one function to rule them all" ;-) Don't check for the combobox contents and then sort basing on another external function call: just call a single function that checks for the combobox and the checkbox, then sort the contents; remember: while OOP is about modularity, you should avoid unnecessary fragmentation (remember the KISS principle);
You should probably not connect to the activated signal, and you should certainly not use the [str] overload (checking for a string is rarely a good idea for index based widgets); use currentIndex() and its currentIndexChanged() signal instead;
Using fixed geometries for child widgets is something that should be avoided as much as possible; layout managers exist for lots of very good reasons (for example, I couldn't click on the QLineEdit if the mouse cursor was too close to the check box, even if it's over the line edit); note that you should avoid using setGeometry for the top level window also;
You should never set instance attributes that are going to be overwritten by a different object: you're continuously overwriting them (self.label, self.textbox, etc.), and the result is that making them instance attributes is completely useless; since you're already creating them with a parent widget, the garbage collector won't delete them at the end of the function, so you should only use local variables instead (label = QLabel(), etc...);
Using a class like you did is not very useful: you're going to group all those widgets anyway, so it's better to use a container widget which not only will manage its children (see the point above about layouts), but will "centralize" all necessary programming logic;
Avoid similar names for class and variables if they refer to different object types: in your case, app refers to the QApplication instance, but App is a QMainWidget subclass;
Here's a possible reimplementation of your code:
from PyQt5 import QtCore, QtWidgets
class TaskWidget(QtWidgets.QWidget):
priorityChanged = QtCore.pyqtSignal()
lastChanged = QtCore.QDateTime.currentDateTime()
def __init__(self, title='', parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.checkBox = QtWidgets.QCheckBox()
layout.addWidget(self.checkBox)
self.textBox = QtWidgets.QLineEdit(title)
layout.addWidget(self.textBox)
self.textBox.setMinimumWidth(240)
self.priorityCombo = QtWidgets.QComboBox()
layout.addWidget(self.priorityCombo)
self.priorityCombo.addItems([
'No priority',
'Priority 1',
'Priority 2',
'Priority 3',
])
self.priorityCombo.currentIndexChanged.connect(self.updatePriority)
self.checkBox.toggled.connect(self.updatePriority)
def updatePriority(self):
# update the "lastChanged" variable, ensuring that the sorting puts on
# top the last changed task
self.lastChanged = QtCore.QDateTime.currentDateTime()
self.priorityChanged.emit()
def priority(self):
priority = self.priorityCombo.currentIndex()
# set an arbitrary (but still reasonable) priority that would be fine for
# more items in the same priority
if priority > 0:
priority *= 100
else:
priority = self.priorityCombo.count() * 100
if self.checkBox.isChecked():
priority -= 1
return priority, self.lastChanged.msecsTo(QtCore.QDateTime.currentDateTime())
class TaskApp(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(central)
self.setCentralWidget(central)
self.taskLayout = QtWidgets.QGridLayout()
layout.addLayout(self.taskLayout)
self.addTaskButton = QtWidgets.QPushButton('Add task')
layout.addWidget(self.addTaskButton)
self.addTaskButton.clicked.connect(self.addTask)
self.tasks = []
self.addTask()
def addTask(self):
task = TaskWidget('Task no. {}'.format(len(self.tasks) + 1))
if not self.taskLayout.count():
# this is for the first item only; we cannot use rowCount, since it
# always returns at least 1, even if it's empty
row = 0
else:
row = self.taskLayout.rowCount()
self.taskLayout.addWidget(QtWidgets.QLabel(str(row + 1)))
self.taskLayout.addWidget(task, row, 1)
self.tasks.append(task)
task.priorityChanged.connect(self.sortTasks)
def sortTasks(self):
tasks = self.tasks[:]
self.tasks.clear()
# since we're using a QGridLayout, we don't need to relate to
# insertWidget(), which is index based and might result in some
# inconsistencies while "regenerating" the UI; in this case, the
# grid will be the same, we're only going to rearrange the child widgets
for row, taskWidget in enumerate(sorted(tasks, key=lambda w: w.priority())):
self.taskLayout.addWidget(taskWidget, row, 1)
self.tasks.append(taskWidget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TaskApp()
w.show()
sys.exit(app.exec_())
I just guessed the priority implementation, but that's not the point.
I also added a datetime check, so that the last changed item with the same priority will always be on top.

My toplevel window in tkinter is no longer being destroyed. It was working fine until I tried changing other aspects of my function

I'm trying to get a popup window to display random text and a picture every time a button is pressed in tkinter. My original code was going to use an if/elif statement to do this. It worked as intended but I thought it might be easier to pair the data in a dictionary since there would be 50 elif statements otherwise (is it frowned upon to use so many? I actually found it easier to read).I was able to get this working but now the toplevel window in tkinter is not being destroyed like it was in the original function. A new Label is just being created on top of it and I can't figure out why. The function code is below. Thanks in advance, any help would be appreciated!
def Add_Gemstone2():
global Addstone
#destroy the previous window if there is one.
try:
AddStone.destroy()
except(AttributeError, NameError):
pass
#create the window.
AddStone=Toplevel()
AddStone.configure(bg='White', height=200, width=325)
AddStone.geometry('325x180+10+100')
# add gemstones to list from file.
gem_stones = open('gemstones.txt')
all_gem_stones = gem_stones.readlines()
gemstones = []
for i in all_gem_stones:
gemstones.append(i.rstrip())
# Add pictures to list.
path = r'C:\Users\Slack\Desktop\PYTHON WORKS\PYTHON GUI PROJECT\gems'
gempictures = []
# r=root, d=directories, f = files
for r,d,f in os.walk(path):
for file in f:
if '.gif' in file:
gempictures.append(os.path.join(r, file))
#create dictionary from lists.
gemdiction = dict(zip(gemstones, gempictures))
key, val = random.choice(list(gemdiction.items()))
# create the labels.
glbl1 = Label(AddStone, text=key, bg='gold', wraplength=300)
glbl1.pack()
image = ImageTk.PhotoImage(Image.open(val))
glbl2 = Label(AddStone, image=image)
glbl2.image = image
glbl2.pack()

What is a reasonable widget count in a GTK3 application?

We have a Python GTK application with a large number of widgets 3-4k.
When porting from GTK2 to GTK3 we noticed a rather big performance hit when:
Adding widgets.
Simply showing (not creating) windows.
My question is: Is this a bug, or is it simply unreasonable to use many thousands of widgets in a GTK3 application?
The following test program perf.py shows both problems:
import sys
import gi
import time
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
label = Gtk.Label("The snappiness of this dialog depends on the number of \nwidgets in the main program.")
self.get_content_area().add(label)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self, count):
Gtk.Window.__init__(self, title="Dialog Example")
box = Gtk.Box()
self.add(box)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
box.pack_start(button, expand=False, fill=True, padding=0)
# Add dummy widgets to show effect
for n in xrange(count):
button = Gtk.Button(str(n))
button.set_no_show_all(True)
t = time.time()
box.pack_start(button, expand=False, fill=True, padding=0)
print('{}\t{:.3f}'.format(n, 1000*(time.time()-t)))
def on_button_clicked(self, widget):
dialog = DialogExample(self)
dialog.run()
dialog.destroy()
count = int(sys.argv[1])
win = DialogWindow(count)
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
The time to add a widget increases for each widget.
The time it takes to display a dialog is affected by the total widget count in the application.
The Python script takes a command line parameter which is the number of (hidden) widgets to create and prints the existing widget count and the time to add it.
$ python perf.py 20000
0 0.026
1 0.021
...
20000 1.700
At 20000 widgets, point 1 is pretty clear even on fast hardware. Point 2 might require slower hardware or double the widget count to be clearly visible.
The image below displays the widget creation time that goes from around 20 microseconds to 6 ms.
Number one is only somewhat unexpected, but the second point was totally unexpected to me.
I have tested this in Gtk 3.18 on three different machines.
Part of the main application window:

psychopy polygon on top of image

using psychopy ver 1.81.03 on a mac I want to draw a polygon (e.g. a triangle) on top of an image.
So far, my image stays always on top and thus hides the polygon, no matter the order I put them in. This also stays true if I have the polygon start a frame later than the image.
e.g. see inn the code below (created with the Builder before compiling) how both a blue square and a red triangle are supposed to start at frame 0, but when you run it the blue square always covers the red triangle!?
Is there a way to have the polygon on top? Do I somehow need to merge the image and polygon before drawing them?
Thank you so much for your help!!
Sebastian
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This experiment was created using PsychoPy2 Experiment Builder (v1.81.03), Sun Jan 18 20:44:26 2015
If you publish work using this script please cite the relevant PsychoPy publications
Peirce, JW (2007) PsychoPy - Psychophysics software in Python. Journal of Neuroscience Methods, 162(1-2), 8-13.
Peirce, JW (2009) Generating stimuli for neuroscience using PsychoPy. Frontiers in Neuroinformatics, 2:10. doi: 10.3389/neuro.11.010.2008
"""
from __future__ import division # so that 1/3=0.333 instead of 1/3=0
from psychopy import visual, core, data, event, logging, sound, gui
from psychopy.constants import * # things like STARTED, FINISHED
import numpy as np # whole numpy lib is available, prepend 'np.'
from numpy import sin, cos, tan, log, log10, pi, average, sqrt, std, deg2rad, rad2deg, linspace, asarray
from numpy.random import random, randint, normal, shuffle
import os # handy system and path functions
# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)
# Store info about the experiment session
expName = u'test_triangle_over_square' # from the Builder filename that created this script
expInfo = {'participant':'', 'session':'001'}
dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)
if dlg.OK == False: core.quit() # user pressed cancel
expInfo['date'] = data.getDateStr() # add a simple timestamp
expInfo['expName'] = expName
# Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
filename = _thisDir + os.sep + 'data/%s_%s_%s' %(expInfo['participant'], expName, expInfo['date'])
# An ExperimentHandler isn't essential but helps with data saving
thisExp = data.ExperimentHandler(name=expName, version='',
extraInfo=expInfo, runtimeInfo=None,
originPath=None,
savePickle=True, saveWideText=True,
dataFileName=filename)
#save a log file for detail verbose info
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING) # this outputs to the screen, not a file
endExpNow = False # flag for 'escape' or other condition => quit the exp
# Start Code - component code to be run before the window creation
# Setup the Window
win = visual.Window(size=(1280, 800), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[0,0,0], colorSpace='rgb',
blendMode='avg', useFBO=True,
)
# store frame rate of monitor if we can measure it successfully
expInfo['frameRate']=win.getActualFrameRate()
if expInfo['frameRate']!=None:
frameDur = 1.0/round(expInfo['frameRate'])
else:
frameDur = 1.0/60.0 # couldn't get a reliable measure so guess
# Initialize components for Routine "trial"
trialClock = core.Clock()
ISI = core.StaticPeriod(win=win, screenHz=expInfo['frameRate'], name='ISI')
square = visual.ImageStim(win=win, name='square',units='pix',
image=None, mask=None,
ori=0, pos=[0, 0], size=[200, 200],
color=u'blue', colorSpace='rgb', opacity=1,
flipHoriz=False, flipVert=False,
texRes=128, interpolate=True, depth=-1.0)
polygon = visual.ShapeStim(win=win, name='polygon',units='pix',
vertices = [[-[200, 300][0]/2.0,-[200, 300][1]/2.0], [+[200, 300][0]/2.0,-[200, 300][1]/2.0], [0,[200, 300][1]/2.0]],
ori=0, pos=[0, 0],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=u'red', fillColorSpace='rgb',
opacity=1,interpolate=True)
# Create some handy timers
globalClock = core.Clock() # to track the time since experiment started
routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine
#------Prepare to start Routine "trial"-------
t = 0
trialClock.reset() # clock
frameN = -1
# update component parameters for each repeat
# keep track of which components have finished
trialComponents = []
trialComponents.append(ISI)
trialComponents.append(square)
trialComponents.append(polygon)
for thisComponent in trialComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "trial"-------
continueRoutine = True
while continueRoutine:
# get current time
t = trialClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *square* updates
if frameN >= 0 and square.status == NOT_STARTED:
# keep track of start time/frame for later
square.tStart = t # underestimates by a little under one frame
square.frameNStart = frameN # exact frame index
square.setAutoDraw(True)
# *polygon* updates
if frameN >= 0 and polygon.status == NOT_STARTED:
# keep track of start time/frame for later
polygon.tStart = t # underestimates by a little under one frame
polygon.frameNStart = frameN # exact frame index
polygon.setAutoDraw(True)
# *ISI* period
if t >= 0.0 and ISI.status == NOT_STARTED:
# keep track of start time/frame for later
ISI.tStart = t # underestimates by a little under one frame
ISI.frameNStart = frameN # exact frame index
ISI.start(0.5)
elif ISI.status == STARTED: #one frame should pass before updating params and completing
ISI.complete() #finish the static period
# check if all components have finished
if not continueRoutine: # a component has requested a forced-end of Routine
routineTimer.reset() # if we abort early the non-slip timer needs reset
break
continueRoutine = False # will revert to True if at least one component still running
for thisComponent in trialComponents:
if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
continueRoutine = True
break # at least one component has not yet finished
# check for quit (the Esc key)
if endExpNow or event.getKeys(keyList=["escape"]):
core.quit()
# refresh the screen
if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
win.flip()
else: # this Routine was not non-slip safe so reset non-slip timer
routineTimer.reset()
#-------Ending Routine "trial"-------
for thisComponent in trialComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
win.close()
core.quit()
As per Jonas' comment above, PsychoPy uses a layering system in which subsequent stimuli are drawn on top of previous stimuli (as in his code examples).
In the graphical Builder environment, drawing order is represented by the vertical order of stimulus components: stimuli at the top are drawn first, and ones lower down are progressively layered upon them.
You can change the order of stimulus components by right-clicking on them and selecting "Move up", "move down", etc as required.
Sebastian, has, however, identified a bug here, in that the intended drawing order is not honoured between ImageStim and ShapeStim components. As a work-around, you might be able to replace your ShapeStim with a bitmap representation, displayed using an ImageStim. Multiple ImageStims should draw correctly (as do multiple ShapeStims). To get it to draw correctly on top of another image, be sure to save it as a .png file, which supports transparency. That way, only the actual shape will be drawn on top, as its background pixels can be set to be transparent and will not mask the the underlying image.
For a long-term solution, I've added your issue as a bug report to the PsychoPy GitHub project here:
https://github.com/psychopy/psychopy/issues/795
It turned out to be a bug in the Polygon component in Builder.
This is fixed in the upcoming release (1.82.00). The changes needed to make the fix can be seen at
https://github.com/psychopy/psychopy/commit/af1af9a7a85cee9b4ec8ad5e2ff1f03140bd1a36
which you can add to your own installation if you like.
cheers,
Jon

Python 3.1 Tkinter layout help. I am close, please help me finish this

I am using Python 3.1 by the way.
I am trying to build a simple GUI using Tkinter - label, text entry field, button on the first row and editable text area with scrollbar to the right and on the bottom of it - on the second row. Please help me fix up the layout. What I have below does not quite work. If I have to use a grid, I will. I wish to keep the code very simple - I want to "sell" Python to some of my coworkers. So, I want to get a somewhat decent look and feel. Suggest better padding if you do not mind. Also, if my variable names, etc. seem weird, then please make a note.
At the same time I want to pretend that this is a throw-away script which I have not spent much time on. Since I am asking for your help, it ain't so, but they do not need to know ;). So, I do not want to introduce fancy code to create nice borders, etc. I just want something that is visually appealing, clean and simple. If I do not, then my presentation will not achieve its goal.
Thank you, my code is below:
class App:
def __init__(self, parent):
frame = Frame(parent)
self.__setup_gui(frame) # Call Helper
frame.pack(padx=15, pady=15)
parent.title('To be changed')
def __setup_gui(self, frame):
# First Row
self.cs_label = Label(frame, text='Change Set: ')
self.cs_label.pack(side=LEFT, padx=10, pady=10)
self.cs_val = Entry(frame, width=10)
self.cs_val.pack(side=LEFT, padx=10, pady=10)
self.get_button = Button(frame, text='Get', command=self.get_content)
self.get_button.pack(side=LEFT, padx=10, pady=10)
# Text area and scrollbar
self.text_area = Text(frame, height=10, width=50, background='white')
# Put a scroll bar in the frame
scroll = Scrollbar(frame)
self.text_area.configure(yscrollcommand=scroll.set)
self.text_area.pack(side=TOP)
scroll.pack(side=RIGHT,fill=Y)
self.clipboard_var = IntVar()
self.notepad_var = IntVar()
def get_content(self):
print(self.clipboard_var.get())
print(self.notepad_var.get())
###################################################################################################
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
You definitely want the grid manager -- Pack only works for a vertical or horizontal stackup by itself. You can use multiple frames to work around it, but I find it's easier to expand a GUI if you just do it with Grid to start.
Here's what I've worked up real quick based what you said and the code. I reduced/removed the padding -- it looked huge for me -- and I set up two scrollbars, in a subframe to make the padding work out more easily. Note that to make the horizontal scrollbar useful your Text area needs to have wrap=NONE; otherwise you might as well use the easy 'ScrolledText' widget from tkinter.scrolledtext and skip the horizontal scroll bar.
I've now reframed things a bit to allow for resize, with a minimum size that shows the top buttons -- see the uses of minsize and row/columnconfigure.
BTW, it looks like your variables aren't being pulled from anywhere -- is that intentional?
from tkinter import *
class App:
def __init__(self, parent):
self.__setup_gui(parent) # Call Helper
parent.title('To be changed')
def __setup_gui(self, parent):
# First Row
self.rowframe = Frame(parent)
self.rowframe.grid()
self.cs_label = Label(self.rowframe, text='Change Set: ')
self.cs_label.grid(row=0, column=0, padx=2, pady=2)
self.cs_val = Entry(self.rowframe, width=10)
self.cs_val.grid(row=0, column=1, padx=2, pady=2)
self.get_button = Button(self.rowframe, text='Get', command=self.get_content)
self.get_button.grid(row=0, column=2, padx=2, pady=2)
parent.update_idletasks()
parent.minsize(width=self.rowframe.winfo_width(), height=self.rowframe.winfo_height())
# Text area and scrollbars
self.textframe = Frame(parent)
self.textframe.grid(row=1, columnspan=2, padx=2, pady=2, sticky=N+S+E+W)
self.hscroll = Scrollbar(self.textframe, orient=HORIZONTAL)
self.vscroll = Scrollbar(self.textframe)
self.text_area = Text(self.textframe, height=10, width=50, wrap=NONE, background='white', yscrollcommand=self.vscroll.set, xscrollcommand=self.hscroll.set)
self.text_area.grid(row=0, column=0, sticky=N+S+E+W)
self.hscroll.config(command=self.text_area.xview)
self.hscroll.grid(row=1, column=0, sticky=E+W)
self.vscroll.config(command=self.text_area.yview)
self.vscroll.grid(row=0, column=1, sticky=N+S)
# Row 0 defaults to 0
parent.rowconfigure(1, weight=1)
parent.columnconfigure(1, weight=1)
# Textarea setup
self.textframe.rowconfigure(0, weight=1)
self.textframe.columnconfigure(0, weight=1)
self.clipboard_var = IntVar()
self.notepad_var = IntVar()
def get_content(self):
print(self.clipboard_var.get())
print(self.notepad_var.get())
###################################################################################################
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
Now, all that said...you might get more visual appeal with PyGTK, PyQt, or wxPython, though tkinter coming "standard" is a nice feature.

Resources