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

Você não tem permissão para executar esta ação.

Excluir mensagem

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)