This repository has been archived by the owner on Jun 26, 2018. It is now read-only.
/
Calculator.py
272 lines (245 loc) · 10.5 KB
/
Calculator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
from tkinter import *
from IOPanel import *
from Digit import *
from GridPositioner import *
from Stack import *
from Operation import *
from StackPanel import *
from BaseMenu import *
from HelpMenu import *
from math import ceil
from ProgramConstants import OPERATORS, \
CLEAR_STACK_DEFAULT, DISPLAY_STACK_DEFAULT
from OptionsMenu import *
# Class for a GUI-based calculator.
class Calculator( Tk ) :
# Width of @IOPanel@ in pixels.
__IO_PANEL_WIDTH = 200
# Height of @IOPanel@ in pixels.
__IO_PANEL_HEIGHT = 50
# Row number of @IOPanel@ in grid layout of the calculator.
__IO_PANEL_ROW = 0
# Column number of @IOPanel@ in grid layout of the calculator.
__IO_PANEL_COL = 0
# Span of @IOPanel@ in widgets in the grid layout of the calculator.
__IO_PANEL_SPAN = 3
# The default base of the calculator.
__BASE = 10
# The title of this calculator's window.
__TITLE = "Calculator"
#The title of the Base selection menu
__BASE_MENU_TITLE = 'Base'
#The title of the Help menu
__HELP_MENU_TITLE = 'Help'
#The title of the Options Menu
__OPTIONS_MENU_TITLE = "Options"
# Row number of the first digit row in grid layout of the calculator.
__DIGIT_ROW = 1
# Column number of the first digit row in grid layout of the calculator.
__DIGIT_COL = 0
# Number of digit buttons per row in grid layout of the calculator.
__DIGITS_PER_ROW = 3
# Text on the clear button.
__CLEAR_TITLE = "C"
# Text on the push button.
__PUSH_TITLE = "P"
#The operator for Clear Everything button
__CLEAR_EVERYTHING_TITLE = 'CE'
#Sticky for the stack panel
__STACK_STICKY = 'NS'
#String for recognising errors from operations
__ERROR_TAG = 'Error'
# Main constructor.
# @parent@: The master widget of this @Calculator@ or @None@
# @base@: The number base for this @Calculator@.
def __init__( self, master, title=__TITLE, base=__BASE) :
self.__base = base
#INITIALIZE THE STACK
self.__stack = Stack()
#Initialise the Operation class
self.__operation = Operation(self.__stack)
# Initialise main calculator window.
Tk.__init__( self, master )
# Set title.
self.title( title )
#Set not resizable
self.resizable(0,0)
# Save @master@. Not used...
self.__master = master
# Finish rest of initialisation.
self.__initialise( base=base)
# Utility method for initialising this @Calculator@'s components.
# @base@: the number base of this @Calculator@'s operations.
def __initialise( self, base,clearOption=CLEAR_STACK_DEFAULT,
displayOption=DISPLAY_STACK_DEFAULT) :
self.__clearStack = clearOption
self.__displayStack = displayOption
# Initialise the IO panel component.
self.__initialiseIOPanel( )
# Initialise the digit panel component.
self.__initialiseDigitPanel( base=base)
#Initialise the operand panel component
self.__initialiseOperandPanel()
#Initialise the menu bar
self.__initialiseMenu()
#Add the Base Change dropdown
self.__initialiseBaseMenu(base)
#Add the Options dropdown
self.__initialiseOptionsMenu()
#Add the Help dropdown
self.__initialiseHelpMenu()
#Initialise the stack display panel, if the option is selected
if self.__displayStack:
self.__initialiseStackPanel()
# Initialise the digit panel widget of this @Calculator@.
# @base@: the number base of this @Calculator@'s operations.
# @row@: row number in grid layout of this @Calculator@.
# @col@: column number in grid layout of this @Calculator@.
# @digitsPerRow@: digits per row in grid layout of this @Calculator@.
def __initialiseDigitPanel( self,
base,
row=__DIGIT_ROW,
col=__DIGIT_COL,
digitsPerRow=__DIGITS_PER_ROW ) :
appendee = self.__iopanel
self.__base = base
self.__positioner = GridPositioner( row=row, col=col,
columns=digitsPerRow )
for digit in [ digit for digit in range( 1, base ) ] + [ 0 ] :
button = Digit( master=self, digit=digit, appendee=appendee )
self.__positioner.add( button )
self.__addSpecialDigitPanelButton( text=Calculator.__CLEAR_TITLE,
command=self.__onClearButtonClick )
self.__addSpecialDigitPanelButton( text=Calculator.__PUSH_TITLE,
command=self.__onPushButtonClick )
# Utility method for adding additional button to the digit panel.
# @text@: the text on the button.
# @command@: the button's callback method.
def __addSpecialDigitPanelButton( self, text, command ) :
button = Button( master=self, text=text, command=command )
self.__positioner.add( button )
# Initialise the IO panel widget of this @Calculator@.
def __initialiseIOPanel( self ) :
width = Calculator.__IO_PANEL_WIDTH
height = Calculator.__IO_PANEL_HEIGHT
# create the IO panel.
iopanel = IOPanel( master=self, width=width, height=height )
row = Calculator.__IO_PANEL_ROW
col = Calculator.__IO_PANEL_COL
span = Calculator.__IO_PANEL_SPAN
# Add the IO panel to the current crid layout.
iopanel.grid( row=row, column=col, columnspan=span )
# Save object reference to the IO panel for future use.
self.__iopanel = iopanel
def __initialiseOperandPanel( self ):
#Add the Operand buttons to the panel
operators = OPERATORS
for operand in operators:
command = lambda operand=operand:self.__onOperandButtonClick(
operand)
self.__addSpecialDigitPanelButton(operand,
command)
#Add the Clear Everything Button
title = Calculator.__CLEAR_EVERYTHING_TITLE
self.__addSpecialDigitPanelButton(title,
self.__onClearEverythingButtonClick)
def __initialiseMenu(self):
#Initialises the menu bar
self.__menu = Menu(self)
self.config(menu=self.__menu)
def __initialiseBaseMenu(self, base) :
#Create the dropdown for selecting the base and add it to the
#menu
baseDropDown = BaseMenu(self, base)
label = Calculator.__BASE_MENU_TITLE
self.__menu.add_cascade(label=label, menu=baseDropDown)
def __initialiseOptionsMenu(self):
self.__options = OptionMenu(self,
self.__clearStack, self.__displayStack)
label = Calculator.__OPTIONS_MENU_TITLE
self.__menu.add_cascade(label=label, menu=self.__options)
def __initialiseHelpMenu(self):
#Initialises the panel for giving help options. ie. instructions
helpMenu = HelpMenu(self)
label = Calculator.__HELP_MENU_TITLE
self.__menu.add_cascade(label=label,menu=helpMenu)
def __initialiseStackPanel(self):
#Initialises the side panel that displays the current stack.
height = Calculator.__IO_PANEL_HEIGHT
width = Calculator.__IO_PANEL_WIDTH
self.__stackPanel = StackPanel(master=self,height=height,width=width,
stack=self.__stack)
self.__showStack()
def __showStack(self) :
#A method for adding the stack panel to the window
#This gets the last row used in the window
rows = ceil(self.__positioner.addedWidgets /
Calculator.__DIGITS_PER_ROW) + 1
sticky = Calculator.__STACK_STICKY
row = Calculator.__IO_PANEL_ROW
col = Calculator.__DIGITS_PER_ROW + 1 #Next to the rest of the stuff
self.__stackPanel.grid(row=row, column=col,
rowspan=rows, sticky=sticky)
self.__stackPanel.update()
def __hideStack(self) :
#A method for hiding the stack panel
self.__stackPanel.grid_forget()
def onStackDisplayChange(self) :
#A method for handling when the user changes the display stack option
displayStack = self.__options.displayStack.get()
if displayStack :
#The stack is hidden, we need to add it back to view
self.__showStack()
else :
#The stack is already visible, hide it
self.__hideStack()
# Callback method for push button
def __onPushButtonClick( self ) :
self.__pushInput()
def __pushInput(self) :
#push the value of the input field onto the stack
var = self.__iopanel.get( )
if var != "":
self.__stack.push(var)
self.__stackPanel.update()
self.__iopanel.reset( )
# Callback method for clear button
def __onClearButtonClick( self ) :
self.__iopanel.reset( )
#Handle presses of operand buttons
def __onOperandButtonClick(self, operand) :
#Handle the clicking of operand buttons
#Push any input in the field onto the stack, if any
self.__pushInput()
#Run the apply function, then display the answer
answer = self.__operation.apply(operand,self.__base)
if answer != None :
#We don't want to display None in the output field
self.__iopanel.set(answer)
if Calculator.__ERROR_TAG in answer :
#If the last operation gave us an error,
#we want to remove it from the stack
self.__stack.pop()
self.__stackPanel.update()
def changeBase(self, newBase) :
#Changes between the given bases
self.__removeAllChildren()
clearStack = self.__options.clearStack.get()
displayStack = self.__options.displayStack.get()
self.__stack.clear() if clearStack else self.__operation.convertStack(
self.__stack,newBase,self.__base)
self.__initialise(newBase,clearStack,displayStack)
def __removeAllChildren(self) :
#Removes all the children from self
for widget in self.winfo_children() :
#Destroy all widgets in self, without destroying self
widget.destroy()
def __onClearEverythingButtonClick(self):
#clear the stack
self.__stack.clear()
self.__iopanel.set("")
self.__stackPanel.update()
self.__iopanel.reset()
if __name__ == "__main__" :
calculator = Calculator( None )
calculator.mainloop( )