-
-
Notifications
You must be signed in to change notification settings - Fork 55
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
Clean up to make building MacOS Package work... #77
base: master
Are you sure you want to change the base?
Changes from 5 commits
d08ad36
4fada59
3a7a862
7b33bff
fe33e44
a134473
3a6449f
22a6f20
7ddc5f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||
import setuptools | ||||||
|
||||||
with open("readme.md", "r") as f: | ||||||
long_description = f.read() | ||||||
|
||||||
setuptools.setup( | ||||||
name="open-mcr", | ||||||
url = "https://github.com/gutow/open-mcr", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should make this the URL of the original repo:
Suggested change
|
||||||
version="1.3.0dev", | ||||||
description="Graphical tool for optical mark recognition for test scoring.", | ||||||
long_description=long_description, | ||||||
long_description_content_type="text/markdown", | ||||||
author="Ian Sanders", | ||||||
author_email="[email protected]", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd just prefer to use my personal email for this (I might not work at GitHub forever 😄):
Suggested change
|
||||||
keywords="omr, optical-mark-recognition, open-educational-resources, " \ | ||||||
"exam-sheets, exam-scoring", | ||||||
license="GPL-3.0+", | ||||||
packages=setuptools.find_packages(exclude=("dist","build",)), | ||||||
data_files=[ | ||||||
('src/assets',['src/assets/icon.ico','src/assets/icon.svg', | ||||||
'src/assets/multiple_choice_sheet_75q.pdf', | ||||||
'src/assets/multiple_choice_sheet_75q.svg', | ||||||
'src/assets/multiple_choice_sheet_150q.pdf', | ||||||
'src/assets/multiple_choice_sheet_150q.svg', | ||||||
'src/assets/social.png', 'src/assets/wordmark.png', | ||||||
'src/assets/wordmark.svg', 'src/assets/manual.md', | ||||||
'src/assets/manual.pdf']) | ||||||
], | ||||||
include_package_data=True, | ||||||
install_requires=[ | ||||||
'PyInstaller>=4.6', | ||||||
'Pillow==9.2.0', | ||||||
'numpy==1.22.0', | ||||||
'opencv-python==4.5.4.60', | ||||||
'flake8==3.7.8', | ||||||
'yapf==0.28.0', | ||||||
'pytest==6.2.5', | ||||||
], | ||||||
classifiers=[ | ||||||
'Development Status :: 5 - Production/Stable', | ||||||
'Intended Audience :: Developers', | ||||||
'Intended Audience :: Education', | ||||||
'Intended Audience :: End Users/Desktop', | ||||||
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', | ||||||
'Programming Language :: Python :: 3', | ||||||
'Operating System :: OS Independent' | ||||||
] | ||||||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import tkinter as tk | ||
import platform | ||
|
||
|
||
# ************************ | ||
# Scrollable Frame Class | ||
# ************************ | ||
class ScrollFrame(tk.Frame): | ||
""" | ||
This is based on the code from this GIST: ttps://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01 | ||
|
||
This class provides a scrollable frame as a nearly drop-in replacement for a tkframe. | ||
|
||
Add items to the ScrollFrame.viewPort, rather than to the ScrollFrame. | ||
``` | ||
class Example(tk.Frame): | ||
def __init__(self, root): | ||
tk.Frame.__init__(self, root) | ||
self.scrollFrame = ScrollFrame(self) # add a new scrollable frame. | ||
|
||
# Now add some controls to the scrollframe. | ||
# NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself) | ||
for row in range(100): | ||
a = row | ||
tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1", | ||
relief="solid").grid(row=row, column=0) | ||
t = "this is the second column for row %s" % row | ||
tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.printMsg("Hello " + str(x))).grid( | ||
row=row, column=1) | ||
|
||
# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort) | ||
self.scrollFrame.pack(side="top", fill="both", expand=True) | ||
|
||
def printMsg(self, msg): | ||
print(msg) | ||
|
||
|
||
if __name__ == "__main__": | ||
root = tk.Tk() | ||
Example(root).pack(side="top", fill="both", expand=True) | ||
root.mainloop() | ||
``` | ||
""" | ||
def __init__(self, parent): | ||
super().__init__(parent) # create a frame (self) | ||
|
||
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff") # place canvas on self | ||
self.viewPort = tk.Frame(self.canvas, | ||
background="#ffffff") # place a frame on the canvas, this frame will hold the child widgets | ||
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) # place a scrollbar on self | ||
self.canvas.configure(yscrollcommand=self.vsb.set) # attach scrollbar action to scroll of canvas | ||
|
||
self.vsb.pack(side="right", fill="y") # pack scrollbar to right of self | ||
self.canvas.pack(side="left", fill="both", expand=True) # pack canvas to left of self and expand to fil | ||
self.canvas_window = self.canvas.create_window((4, 4), window=self.viewPort, anchor="nw", | ||
# add view port frame to canvas | ||
tags="self.viewPort") | ||
|
||
self.viewPort.bind("<Configure>", | ||
self.onFrameConfigure) # bind an event whenever the size of the viewPort frame changes. | ||
self.canvas.bind("<Configure>", | ||
self.onCanvasConfigure) # bind an event whenever the size of the canvas frame changes. | ||
|
||
self.viewPort.bind('<Enter>', self.onEnter) # bind wheel events when the cursor enters the control | ||
self.viewPort.bind('<Leave>', self.onLeave) # unbind wheel events when the cursorl leaves the control | ||
|
||
self.onFrameConfigure( | ||
None) # perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize | ||
|
||
def onFrameConfigure(self, event): | ||
'''Reset the scroll region to encompass the inner frame''' | ||
self.canvas.configure(scrollregion=self.canvas.bbox( | ||
"all")) # whenever the size of the frame changes, alter the scroll region respectively. | ||
|
||
def onCanvasConfigure(self, event): | ||
'''Reset the canvas window to encompass inner frame when required''' | ||
canvas_width = event.width | ||
self.canvas.itemconfig(self.canvas_window, | ||
width=canvas_width) # whenever the size of the canvas changes alter the window region respectively. | ||
|
||
def onMouseWheel(self, event): # cross platform scroll wheel event | ||
if platform.system() == 'Windows': | ||
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") | ||
elif platform.system() == 'Darwin': | ||
self.canvas.yview_scroll(int(-1 * event.delta), "units") | ||
else: | ||
if event.num == 4: | ||
self.canvas.yview_scroll(-1, "units") | ||
elif event.num == 5: | ||
self.canvas.yview_scroll(1, "units") | ||
|
||
def onEnter(self, event): # bind wheel events when the cursor enters the control | ||
if platform.system() == 'Linux': | ||
self.canvas.bind_all("<Button-4>", self.onMouseWheel) | ||
self.canvas.bind_all("<Button-5>", self.onMouseWheel) | ||
else: | ||
self.canvas.bind_all("<MouseWheel>", self.onMouseWheel) | ||
|
||
def onLeave(self, event): # unbind wheel events when the cursorl leaves the control | ||
if platform.system() == 'Linux': | ||
self.canvas.unbind_all("<Button-4>") | ||
self.canvas.unbind_all("<Button-5>") | ||
else: | ||
self.canvas.unbind_all("<MouseWheel>") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
import abc | ||
import enum | ||
import os.path | ||
from pathlib import Path | ||
import subprocess | ||
import sys | ||
import tkinter as tk | ||
from tkinter import filedialog, ttk | ||
from tk_scrollable_frame import ScrollFrame | ||
import typing as tp | ||
import platform | ||
|
||
|
@@ -442,7 +444,7 @@ def show_exit_button_and_wait(self): | |
sys.exit(0) | ||
|
||
|
||
class MainWindow: | ||
class MainWindow(tk.Frame): | ||
root: tk.Tk | ||
input_folder: Path | ||
output_folder: Path | ||
|
@@ -469,21 +471,25 @@ def __init__(self): | |
app.iconbitmap(iconpath) | ||
|
||
app.protocol("WM_DELETE_WINDOW", self.__on_close) | ||
app.minsize(500,600) | ||
|
||
# Scrollable Inner Frame | ||
self.scroll = ScrollFrame(app) | ||
|
||
self.__input_folder_picker = InputFolderPickerWidget( | ||
app, self.__on_update) | ||
self.__answer_key_picker = AnswerKeyPickerWidget(app, self.__on_update) | ||
self.scroll.viewPort, self.__on_update) | ||
self.__answer_key_picker = AnswerKeyPickerWidget(self.scroll.viewPort, self.__on_update) | ||
self.__arrangement_map_picker = ArrangementMapPickerWidget( | ||
app, self.__on_update) | ||
self.scroll.viewPort, self.__on_update) | ||
self.__output_folder_picker = OutputFolderPickerWidget( | ||
app, self.__on_update) | ||
self.scroll.viewPort, self.__on_update) | ||
|
||
self.__status_text = tk.StringVar() | ||
status = tk.Label(app, textvariable=self.__status_text) | ||
status = tk.Label(self.scroll.viewPort, textvariable=self.__status_text) | ||
status.pack(fill=tk.X, expand=1, pady=(YPADDING * 2, 0)) | ||
self.__on_update() | ||
|
||
buttons_frame = tk.Frame(app) | ||
buttons_frame = tk.Frame(self.scroll.viewPort) | ||
|
||
# "Open Help" Button | ||
pack(ttk.Button(buttons_frame, | ||
|
@@ -509,6 +515,9 @@ def __init__(self): | |
side=tk.RIGHT) | ||
pack(buttons_frame, fill=tk.X, expand=1) | ||
|
||
# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort) | ||
self.scroll.pack(side="top", fill="both", expand=True) | ||
|
||
self.__ready_to_continue = tk.IntVar(name="Ready to Continue") | ||
app.wait_variable("Ready to Continue") | ||
|
||
|
@@ -609,20 +618,28 @@ def __confirm(self): | |
self.__ready_to_continue.set(1) | ||
|
||
def __show_help(self): | ||
helpfile = str(Path(__file__).parent / "assets" / "manual.pdf") | ||
subprocess.Popen([helpfile], shell=True) | ||
helpfile = helpfile = os.path.join(Path(__file__).parent, "assets", | ||
"manual.pdf") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure For reference, here's the documentation on the slash operator. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have not investigated the newer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep with the current style please, since it's more readable and consistent across all the code. The new |
||
if platform.system() in ('Darwin','Linux'): | ||
subprocess.Popen(['open',helpfile]) | ||
else: | ||
subprocess.Popen([helpfile], shell=True) | ||
|
||
def __show_sheet(self): | ||
if (self.form_variant == FormVariantSelection.VARIANT_75_Q): | ||
helpfile = str( | ||
Path(__file__).parent / "assets" / | ||
helpfile = os.path.join(Path(__file__).parent, "assets", | ||
"multiple_choice_sheet_75q.pdf") | ||
subprocess.Popen([helpfile], shell=True) | ||
if platform.system() in ('Darwin','Linux'): | ||
subprocess.Popen(['open', helpfile]) | ||
else: | ||
subprocess.Popen([helpfile], shell=True) | ||
elif (self.form_variant == FormVariantSelection.VARIANT_150_Q): | ||
helpfile = str( | ||
Path(__file__).parent / "assets" / | ||
"multiple_choice_sheet_150q.pdf") | ||
subprocess.Popen([helpfile], shell=True) | ||
helpfile = os.path.join(Path(__file__).parent, "assets", | ||
"multiple_choice_sheet_150q.pdf") | ||
if platform.system() in ('Darwin','Linux'): | ||
subprocess.Popen(['open', helpfile]) | ||
else: | ||
subprocess.Popen([helpfile], shell=True) | ||
|
||
def __on_close(self): | ||
self.__app.destroy() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Q1,Q2,Q3,Q4,Q5 | ||
,,,,12345678,,a.jpg,A,B,B,D,E | ||
,,,,12334235,,b.jpg,A,,C,D,E | ||
,,,,12345678,,a.jpg,A,B,B,D,E |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Total Score (%),Total Points,Q1,Q2,Q3,Q4,Q5,Q6,Q7,Q8,Q9,Q10,Q11,Q12,Q13,Q14,Q15,Q16,Q17,Q18,Q19,Q20,Q21,Q22,Q23,Q24,Q25,Q26,Q27,Q28,Q29,Q30,Q31,Q32,Q33,Q34,Q35,Q36,Q37,Q38,Q39,Q40,Q41,Q42,Q43,Q44,Q45,Q46,Q47,Q48,Q49,Q50,Q51,Q52,Q53,Q54,Q55,Q56,Q57,Q58,Q59,Q60,Q61,Q62,Q63,Q64,Q65,Q66,Q67,Q68,Q69,Q70,Q71,Q72,Q73,Q74,Q75,Q76,Q77,Q78,Q79,Q80,Q81,Q82,Q83,Q84,Q85,Q86,Q87,Q88,Q89,Q90,Q91,Q92,Q93,Q94,Q95,Q96,Q97,Q98,Q99,Q100,Q101,Q102,Q103,Q104,Q105,Q106,Q107,Q108,Q109,Q110,Q111,Q112,Q113,Q114,Q115,Q116,Q117,Q118,Q119,Q120,Q121,Q122,Q123,Q124,Q125,Q126,Q127,Q128,Q129,Q130,Q131,Q132,Q133,Q134,Q135,Q136,Q137,Q138,Q139,Q140,Q141,Q142,Q143,Q144,Q145,Q146,Q147,Q148,Q149,Q150 | ||
,,,,12345678,,a.jpg,80.0,4,1,1,0,1,1 | ||
,,,,12334235,,b.jpg,80.0,4,1,0,1,1,1 | ||
,,,,12345678,,a.jpg,80.0,4,1,1,0,1,1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
Last Name,First Name,Middle Name,Test Form Code,Student ID,Course ID,Source File,Q1,Q2,Q3,Q4,Q5 | ||
,,,,12334235,,90deg.jpg,A,,C,D,E | ||
,,,,12334235,,180deg.jpg,A,,C,D,E | ||
,,,,12334235,,270deg.jpg,A,,C,D,E | ||
,,,,12334235,,90deg.jpg,A,,C,D,E |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import tkinter as tk | ||
# This standard testing will not work because open-mcr is | ||
# is not built as a python package. | ||
|
||
# def test_init(): | ||
# root = tk.Tk() | ||
# tstframe = ScrollFrame(root) | ||
# assert (isinstance(tstframe,ScrollFrame)) | ||
# assert (isinstance(tstframe,tk.Frame)) | ||
|
||
# TODO more tests |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# TODO the following need tests | ||
# pack | ||
# create_and_pack_label | ||
# PickerWidget | ||
# FolderPickerWidget | ||
# FilePickerWidget | ||
# CheckboxWidget | ||
# SelectWidget | ||
# FormVariantSelection | ||
# InputFolderPickerWidget | ||
# OutputFolderPickerWidget | ||
# AnswerKeyPickerWidget | ||
# ArrangementMapPickerWidget | ||
# ProgressTrackerWidget | ||
# create_and_pack_progress | ||
|
||
# MainWindow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the change to
PyInstaller>=4.6
, but I'm confused by the change fromtyping
(the type-checking tool) toPillow
(an image processing library). Was that an intentional change?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pillow is the python library to use the PIL image library package. I ended up with it for something, maybe PyInstaller. I will look back at it.
typing
is not required for python 3.9+ as it is included. As the current python is 3.10, I am trying to update this to work with that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I will get to the rest of these this weekend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I didn't know that. 👍 to removing the dependency then, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still have to figure out why I had to add Pillow. I think it is for icon building for MacOS.
Revert the scrolling frame changes
Revert the change from using / to join paths (if you do find this causes any issues, please open an issue and I can take another look at it)
Remove the test_ files -- I definitely agree with having more tests but we can open issues instead of adding TODO comments, that way all the work is tracked in one place
Revert the requirements change from typing==3.7.4.1 to Pillow==9.2.0; this looks like a mistake (please correct me if I'm wrong!)