by JoaoBueno
Nunca programei muita coisa em GUI. Hoje inventei de criar uma calculadora como exemplo para um amigo que está aprendendo python.
É impressionante como os kits GUI - todos - são anti-pythonicos. Nas chamadas, acesso a atributos, etc... No quesito ser Pythonico, o Tkinter de fato ganha de longe.
Agora GTK e Qt...nota -5 em documentação. Para conseguir fazer o exemplo, não achei sequer um tutorial dos bindings de python que tivesse coisas tão simples quando o que fazer para acessar o texto de um widget de entrada de texto de uma linha (no Qt até pra saber que esse Widget se chamava QLineEdit sambei). O remédio para Qt e GTK é consultar a documentação na raiz mesmo - do GTK+ em C e do QT4 em C++. O Tkinter tem a referência completa em python (pdf) e dá mais luz quando tentamos usar "help" no console interativo.
Mas, esperoq eu minah calculadorinha 4 operações possa salvar uma ou outra alma por ai, com dicas de como conectar botões em ter que criar uma função para cada botão. (Nesse critério, o mais esotérico é o Tk )- o Qt tá mal documentado pra caramba, mas pelo menos tem só "um jeito óbvi de fazer". Alias - teoricamente, a gambiarra que fiz no Tkinter deveria ter funcionado no Qt também - mas não funciona. Coisas de nossos amigos em C++.
Por fim, desculpas aos puristas de algum desses toolkits - provavelmente passei longe de "fazer do jeito certo" em qualquer das três implementações. O Tkinter sofreu muito por não estar redimensionável (tem como fazer).
Mas por outro lado, o fato do Qt estar mais bonito é culpa, sim, do GTK e do Tk - nesses, para espaçar os botões, por exemplo, eu teria que ficar fazendo ajustes manuais do layout que não sãp do escopo dessa aplicação.
Atualização: o código antes era "bonitnho" e rodava uma calculadora por vez. Alguém na lista PyGTK me deu a idéia de mandar as três rpa tela ao mesmo tempo. (Mas os mainloops da gtk+ e da Tkinter são auto-excludentes dentro do mesmo processo, tive que apelar para o fork)
Coisas que deram mais trabalho:
* descobrir como deixar os botões do GTK+ do mesmo tamanho (tem que se configurar uma fonte mono-espaçada para o label implicito dentro dos botões. Mudar a fonte em GTK, uma vez que tem que usar chamadas de Pango, também não é algo óbvio.)
* conectar os botões do Qt4 sem precisar criar uma função para cada um (é necessario que descobrir qual botão disparou o sinal com uma chamada a self.sender. Parece fácil vendo aqui, mas vá procurar outro exemplo disso na web)
* No TkInter eu já sabia da questão da função callback - então não me estressou hoje. E criar a factorizinha foi divertido.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Copyright João S. O. Bueno (2008)
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public
7 * License as published by the Free Software Foundation; either
8 * version 3 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 * You should have received a copy of the GNU General
14 * Public License along with this program; if not, see
15 * <http://www.gnu.org/licenses/>.
16 """
17 import sys
18
19 class Calculator(object):
20 def __init__(self):
21 self.reset()
22
23 def reset(self):
24 self.set_displayed(0.)
25 self.operator = None
26 self.operand_1 = 0.
27 self.clear_display = True
28
29 def keypress(self, arg):
30 if arg in ("+", "-", "X", "/"):
31 if not self.operator:
32 self.operand_1 = self.get_displayed()
33 else:
34 self.operand_2 = self.get_displayed()
35 self.operate()
36 self.operator = arg
37 self.clear_display = True
38 elif arg == "%":
39 if not self.operand_1:
40 return
41 operand_2 = self.get_displayed()
42 result = self.operand_1 / 100.0 * operand_2
43 self.set_displayed(result)
44 elif arg == "=":
45 if not self.operator:
46 return
47 self.operand_2 = self.get_displayed()
48 self.operate()
49 self.operator = None
50 self.clear_display = True
51 elif arg == "C":
52 self.reset()
53 else:
54 self.insert_number(arg)
55
56
57 def set_displayed(self, number):
58 self.set_displayed_str(("%f" % number).strip("0"))
59
60 def get_displayed(self):
61 try:
62 result = float(self.get_displayed_str())
63 except ValueError:
64 result = 0.0
65 return result
66
67 def insert_number(self, digit):
68 if not self.clear_display:
69 text = self.get_displayed_str()
70 else:
71 text = ""
72 text += digit
73 self.set_displayed_str(text)
74 self.clear_display = False
75
76 def operate (self):
77 if self.operator == "+":
78 result = self.operand_1 + self.operand_2
79 elif self.operator == "-":
80 result = self.operand_1 - self.operand_2
81 elif self.operator == "X":
82 result = self.operand_1 * self.operand_2
83 elif self.operator == "/":
84 if not self.operand_2:
85 self.set_displayed_str("Error")
86 self.operand_1 = 0
87 return
88 result = self.operand_1 / self.operand_2
89 self.operand_1 = result
90 self.set_displayed(result)
91
92 try:
93 import gtk
94 import pango
95 except ImportError:
96 sys.stderr.write("PyGTK not found\n")
97 #create a dummy gtk window class so the program parses
98 class gtk(object):
99 class Window(object):
100 pass
101
102 class GtkCalculator(Calculator, gtk.Window):
103 def __init__(self):
104 gtk.Window.__init__(self)
105 self.connect("destroy", self.destroy)
106 mainbox = gtk.VBox()
107 self.add(mainbox)
108 self.display = gtk.Entry()
109 mainbox.add(self.display)
110 self.display.set_editable(False)
111 self.display.set_alignment(1.0)
112 count = 0
113 font = pango.FontDescription("monospace 16")
114 buttons = "789+456-123X0.%/C="
115 for button in buttons:
116 if count % 4 == 0:
117 row = gtk.HBox()
118 mainbox.pack_start(row)
119 b = gtk.Button(button)
120 #b is a GTK Button - it has no text
121 # it contains a single "gtk label"
122 # to set the button font we have to change the font
123 # in this contained label instead
124 b.get_child().modify_font(font)
125 b.connect("clicked", self.keypress, button)
126 row.pack_start(b)
127
128 count += 1
129 self.show_all()
130 Calculator.__init__(self)
131
132 def keypress(self, widget, arg):
133 Calculator.keypress(self, arg)
134
135 def set_displayed_str(self, number):
136 self.display.set_text(number)
137 def get_displayed_str(self):
138 return self.display.get_text()
139
140
141 def destroy(self, *args):
142 gtk.main_quit()
143
144 try:
145 import Tkinter
146 except ImportError:
147 sys.stderr.write("Tkinter not found\n")
148 #create a dummy Tkinter.Tk class so the program parses
149 class Tkinter(object):
150 class Tk(object):
151 pass
152
153 class TkCalculator(Calculator, Tkinter.Tk):
154 def __init__(self):
155 Tkinter.Tk.__init__(self)
156 self.title("Calculadora")
157 self.displayed = Tkinter.StringVar()
158 self.display = Tkinter.Entry(self,
159 textvar = self.displayed,
160 state = "readonly",
161 justify = Tkinter.RIGHT,
162 background="#dddddd",
163 readonlybackground="#dddddd",
164 borderwidth=5)
165 self.display.grid(row=0, columnspan=4)
166 buttons = "789+456-123X0.%/C="
167 count = 0
168 row = 0
169 for button in buttons:
170 if count % 4 == 0:
171 row += 1
172 b = Tkinter.Button(self,
173 text = button,
174 command = self.command_factory(button),
175 width=2 )
176 b.grid(column=count % 4, row=row)
177 count += 1
178 Calculator.__init__(self)
179
180 def command_factory(self, button):
181 class Command(object):
182 def __init__(self, calculator, button):
183 self.calculator = calculator
184 self.button = button
185 def command(self):
186 return self.calculator.keypress(self.button)
187 return Command(self, button).command
188
189
190 def set_displayed_str(self, string):
191 self.displayed.set(string)
192
193 def get_displayed_str(self):
194 return self.displayed.get()
195
196 try:
197 from PyQt4 import QtGui
198 from PyQt4 import QtCore
199 except ImportError:
200 sys.stderr.write("PyQt4 not found\n")
201 #create a dummy QtGui.QWidget class so the program parses
202 class QtGui(object):
203 class QWidget(object):
204 pass
205 class QtCalculator(Calculator, QtGui.QWidget):
206 def __init__(self):
207 QtGui.QWidget.__init__(self)
208 self.setWindowTitle("Calculator")
209 grid = QtGui.QGridLayout()
210 #FIXME: find single line widget
211 self.display = QtGui.QLineEdit()
212 self.display.setReadOnly(True)
213 self.display.setAlignment(QtCore.Qt.AlignRight)
214 grid.addWidget(self.display,0,0,1, 5)
215 buttons = "789+456-123X0.%/C="
216 counter = 0
217 row = 0
218 self.clear_display = False
219 for button in buttons:
220 if counter % 4 == 0:
221 row += 1
222 b = QtGui.QPushButton()
223 b.setText(button)
224 self.connect(b, QtCore.SIGNAL("clicked()"), self.keypress)
225 grid.addWidget(b, row, counter % 4)
226 counter += 1
227 self.setLayout(grid)
228 self.show()
229 Calculator.__init__(self)
230
231 def keypress(self, *args):
232 button = self.sender()
233 if not isinstance(button, QtGui.QPushButton):
234 return
235 key = str(button.text().toUtf8())
236 Calculator.keypress(self,key)
237
238 def set_displayed_str(self, string):
239 self.display.setText(string)
240
241 def get_displayed_str(self):
242 return str(self.display.text().toUtf8())
243
244 if __name__ == "__main__":
245 if len(sys.argv) >= 2 and sys.argv[1] in ("tk", "gtk", "qt", "all"):
246 if sys.argv[1] == "gtk":
247 GtkCalculator()
248 gtk.main()
249 elif sys.argv[1] == "tk":
250 TkCalculator()
251 Tkinter.mainloop()
252 elif sys.argv[1] == "qt":
253 app = QtGui.QApplication(sys.argv[1:])
254 c = QtCalculator()
255 sys.exit(app.exec_())
256 elif sys.argv[1] == "all":
257 import os
258 if os.fork()==0:
259 app = QtGui.QApplication(sys.argv[1:])
260 c = QtCalculator()
261 GtkCalculator()
262 gtk.main()
263 else:
264 TkCalculator()
265 Tkinter.mainloop()
266 #sys.exit(app.exec_())
267
268 else:
269 sys.stderr.write("Usage: calc_tk_gtk [tk|gtk|qt|all]\n")
270 sys.exit(1)