associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

CalculadoraTkGtkQt

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)