GTK+/Poprawiona wersja programu
Przedstawiony tu program zawiera w sobie te wszystkie poprawki w stosunku do pierwszej wersji, które były opisane podczas analizy programu. Ponadto wprowadza możliwość ciągłego wykonywania działań na przemian - w tym celu została wprowadzona nowa zmienna prev_operation
do struktury Kalkulator
.
Kod programu został skrócony dzięki zastosowaniu dwóch nowych funkcji. Pierwsza z nich to numeric_btn_clicked_event()
: obsługuje ona teraz zadania wszystkich przycisków numerycznych. Wprowadzono tu zmianę dot. zachowania po uzyciu przycisku "=". Gdy zaraz po wciśnięciu klawisza sumy zostanie wybrana jakakolwiek cyfra domniema się, że użytkownik chce wykonać nowe działanie, dlatego wykonywane jest zerowanie danych jak podczas wciśnięcia przycisku "C". Funkcja calculate_btn_clicked_event()
odpowiada na użycie czterech przycisków "+", "-", "*", "/". Dzięki tym dwóm funkcjom pozbyliśmy się powtarzającego się kodu.
Funkcja licz()
została przeprojektowana tak aby była całkowicie niezależna od reszty programu. Wynik jest wyświetlany bez zbędnych zer. Ponieważ teraz funkcja umieszcza wynik w zmiennej na jaką wskazuje przekazany jej wskaźnik konieczne stało się dynamiczne alokowanie pamięci dla wyniku przed użyciem funkcji licz()
. Została do tego użyta wcześniej nieomawiana funkcja g_malloc0(). Alokuje ona pamięć i jednocześnie wypełnia ją zerami. Gdy przestaje być nam potrzebna jest zwalniana.
kalkulator.h
:
struct _Kalkulator {
// pamięć zarządzana przez GTK+
GtkWidget *window;
GtkWidget *table;
GtkWidget *entry;
// pamiec zarządzana przez nas
GString *value1;
GString *value2;
// numer operacji - działania
// 1 +
// 2 -
// 3 *
// 4 /
gint8 operation;
// poprzedni rodzaj działania
gint8 prev_operation;
// stan kalkulatora
gboolean is_value1;
gboolean is_value2;
gboolean is_result;
// ostatnio wciśnięty przycisk
// 1 1
// 2 2
// ...
// 9 9
// 0 0
// 10 +
// 11 -
// 12 *
// 13 /
// 14 =
// 15 ,
// 16 <-
// 17 C
gint8 prev_button;
};
typedef struct _Kalkulator Kalkulator;
kalkulator2.c
:
/*
* Autor: Grzegorz Kuczyński
* kompilacja: gcc -Wall -g kalkulator2.c -o kalkulator2 `pkg-config --cflags --libs gtk+-2.0`
*
* TODO:
* - obsługa przecinka (spr błędów)
* - reakcja na backspace - obcinanie końcówki liczby value1/2 (przycisk <-)
* - sterowanie za pomocą klawiatury numerycznaj
* - komunikat dot. dzielenia przez 0
*/
#include <gtk/gtk.h>
#include "kalkulator.h"
#include <stdlib.h>
#include <string.h>
#define DEBUG
// funkcja debugująca
void info (Kalkulator * pkalkulator, gchar* event)
{
Kalkulator *kalkulator = pkalkulator;
printf (
"\n =============\n \
event: %s \n \
- - - - -\n \
Struktura:\n \
value1: %s \n \
value2: %s \n \
operation: %d \n \
is_value1: %d \n \
is_value2: %d \n \
is_result: %d \n \
prev_button: %d \n \
prev_operation: %d \n \
- - - - -\n",
event,
kalkulator->is_value1 == TRUE? kalkulator->value1->str :"",
kalkulator->is_value2 == TRUE? kalkulator->value2->str :"",
kalkulator->operation,
kalkulator->is_value1,
kalkulator->is_value2,
kalkulator->is_result,
kalkulator->prev_button,
kalkulator->prev_operation);
}
// funkcja odpowiadająca za właściwe wykonywanie opliczeń na 2 wartościach
void licz( const char *s_value1,
const char *s_value2,
char operation,
char *s_result)
{
double d_value1=0,
d_value2=0,
d_result=0;
int len;
// char* -> double
d_value1 = strtod( s_value1, NULL );
d_value2 = strtod( s_value2, NULL );
switch( operation )
{
case 1:
d_result = d_value1 + d_value2;
break;
case 2:
d_result = d_value1 - d_value2;
break;
case 3:
d_result = d_value1 * d_value2;
break;
case 4:
if ( d_value2 != 0.0 )
d_result = d_value1 / d_value2;
else
d_result = d_value1;
break;
}
// double -> char*
len = sprintf( s_result, "%g", d_result );
// można to spr.
// if ( !len ) ...
#ifdef DEBUG
printf ("liczby to %g , %g wynik to: %g \n",d_value1,d_value2,d_result);
#endif
}
// obsłuwa wszystkich przycisków numerycznych: 1,2,3,4,5,6,7,8,9,0
void numeric_btn_clicked_event (gpointer pkalkulator, gchar *number)
{
g_print ("clicked: %s\n",number);
Kalkulator *kalkulator = pkalkulator;
if ( kalkulator->is_result && kalkulator->prev_button == 14 )
{
if (kalkulator->is_value1 == TRUE)
{
g_string_free (kalkulator->value1,TRUE);
kalkulator->value1 = g_string_new (NULL);
}
if (kalkulator->is_value2 == TRUE)
{
g_string_free (kalkulator->value2,TRUE);
kalkulator->value2 = g_string_new (NULL);
}
kalkulator->operation = -1;
kalkulator->prev_operation = -1;
kalkulator->is_value1 = FALSE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = FALSE;
kalkulator->prev_button = -1;
gtk_entry_set_text ( GTK_ENTRY(kalkulator->entry),"");
}
else if ( kalkulator->is_result )
{
gtk_entry_set_text ( GTK_ENTRY( kalkulator->entry ),"");
kalkulator->is_result = FALSE;
}
gtk_entry_append_text ( GTK_ENTRY(kalkulator->entry),number);
kalkulator->prev_button = atoi(number);
}
// obsługa wszytskich przycisków działń: +,-,*,/
void calculate_btn_clicked_event ( gpointer pkalkulator,
gchar *button,
gint8 operation )
{
g_print ("clicked: %s\n",button);
Kalkulator *kalkulator = pkalkulator;
gint8 test = 0,
prev_operation_tmp = FALSE;
gchar *text_entry = GTK_ENTRY(kalkulator->entry)->text;
char *s_result = g_malloc0( 50 ); // free me
// +,-,*,/
kalkulator->operation = operation;
// test
if ( kalkulator->is_value1 == FALSE &&
kalkulator->is_value2 == FALSE &&
strlen (text_entry) > 0 )
{
// nalezy ustawić value1
test = 1;
}
if ( kalkulator->is_value1 == TRUE &&
kalkulator->is_value2 == FALSE &&
strlen (text_entry) > 0 &&
kalkulator->prev_button != 10 && // +
kalkulator->prev_button != 11 && // -
kalkulator->prev_button != 12 && // *
kalkulator->prev_button != 13 && // /
kalkulator->prev_button != 14 && // =
kalkulator->prev_button != 15 ) // ,
{
// ustawic value2 i obliczyć wynik
test = 2;
}
switch (test)
{
case 1:
kalkulator->value1 = g_string_new( gtk_entry_get_text(GTK_ENTRY(kalkulator->entry)) );
kalkulator->is_value1 = TRUE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = FALSE;
gtk_entry_set_text( GTK_ENTRY(kalkulator->entry), "" );
break;
case 2:
kalkulator->value2 = g_string_new( gtk_entry_get_text(GTK_ENTRY(kalkulator->entry)) );
kalkulator->is_value2 = TRUE;
// gdy po wielokrotnych operacji następuje zmiana
// wynik obliczamy według poprzednio wybranej operacji
// np. 5 + 5 (10)- 5 = 15
if ( kalkulator->operation != kalkulator->prev_operation )
prev_operation_tmp = TRUE;
licz( g_string_free (kalkulator->value1, FALSE),
g_string_free (kalkulator->value2, FALSE),
prev_operation_tmp? kalkulator->prev_operation: kalkulator->operation,
s_result );
kalkulator->value1 = g_string_new( s_result );
/*
* kalkulator->value1 (wynik)
* kalkulator->value2 (puste)
*/
gtk_entry_set_text( GTK_ENTRY (kalkulator->entry),
(gchar*)kalkulator->value1->str );
kalkulator->is_value1 = TRUE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = TRUE;
break;
}
// mały trik, działanie '+' to 1 a nr. przycisku '+' to 10
kalkulator->prev_button = (operation+10)-1;
// zabezpiecza nas przed tym:
// np. 1 + 5 * daje w wyniku 5 ponieważ wykonał rodzaj operacji
// domyślny dla obecnie wciśniętego przycisku, czyli *
kalkulator->prev_operation = kalkulator->operation;
g_free( s_result );
#ifdef DEBUG
info(kalkulator,button);
#endif
}
//==========================================
// obsługa zdarzeń przycisków numerycznych
static void btn_1_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"1");
}
static void btn_2_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"2");
}
static void btn_3_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"3");
}
static void btn_4_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"4");
}
static void btn_5_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"5");
}
static void btn_6_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"6");
}
static void btn_7_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"7");
}
static void btn_8_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"8");
}
static void btn_9_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"9");
}
static void btn_0_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
numeric_btn_clicked_event (pkalkulator,"0");
}
// obsługa zdarzeń przycisków funkcyjnych
static void btn_comma_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
g_print ("clicked: ,\n");
Kalkulator *kalkulator = pkalkulator;
gtk_entry_append_text ( GTK_ENTRY(kalkulator->entry),",");
kalkulator->prev_button = 15;
}
static void btn_back_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
g_print ("clicked: <-\n");
Kalkulator *kalkulator = pkalkulator;
GString *tmp=NULL;
tmp = g_string_new( gtk_entry_get_text (GTK_ENTRY(kalkulator->entry)));
tmp = g_string_truncate ( tmp, tmp->len - 1 );
gtk_entry_set_text ( GTK_ENTRY( kalkulator->entry ),
g_string_free( tmp, FALSE ) );
kalkulator->prev_button = 16;
}
static void btn_clear_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
g_print ("clicked: C\n");
Kalkulator *kalkulator = pkalkulator;
if (kalkulator->is_value1 == TRUE)
{
g_string_free (kalkulator->value1,TRUE);
kalkulator->value1 = g_string_new (NULL);
}
if (kalkulator->is_value2 == TRUE)
{
g_string_free (kalkulator->value2,TRUE);
kalkulator->value2 = g_string_new (NULL);
}
kalkulator->operation = -1;
kalkulator->prev_operation = -1;
kalkulator->is_value1 = FALSE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = FALSE;
kalkulator->prev_button = -1;
gtk_entry_set_text ( GTK_ENTRY(kalkulator->entry),"");
kalkulator->prev_button = 17;
#ifdef DEBUG
info(kalkulator,"clear");
#endif
}
// opsługa zdarzeń przycisków działań
static void btn_add_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
calculate_btn_clicked_event (pkalkulator, "+", 1);
}
static void btn_sub_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
calculate_btn_clicked_event (pkalkulator, "-", 2);
}
static void btn_multi_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
calculate_btn_clicked_event (pkalkulator, "*", 3);
}
static void btn_div_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
calculate_btn_clicked_event (pkalkulator, "/", 4);
}
static void btn_result_clicked( GtkWidget *widget,
gpointer pkalkulator )
{
g_print ("clicked: =\n");
Kalkulator *kalkulator = pkalkulator;
// zamiast: ((Kalkulator*)pkalkulator)->operation
gchar *text_entry = GTK_ENTRY(kalkulator->entry)->text;
char *s_result = g_malloc0(50);
if ( kalkulator->is_value1 == TRUE &&
kalkulator->is_value2 == FALSE &&
strlen (text_entry) > 0 &&
kalkulator->prev_button != 10 &&
kalkulator->prev_button != 11 &&
kalkulator->prev_button != 12 &&
kalkulator->prev_button != 13 &&
kalkulator->prev_button != 14 &&
kalkulator->prev_button != 15 )
{
kalkulator->value2 = g_string_new( gtk_entry_get_text(GTK_ENTRY(kalkulator->entry)) );
kalkulator->is_value2 = TRUE;
licz( g_string_free( kalkulator->value1, FALSE ),
g_string_free( kalkulator->value2, FALSE ),
kalkulator->operation,
s_result );
kalkulator->value1 = g_string_new( s_result );
/*
* kalkulator->value1 (wynik)
* kalkulator->value2 (puste)
*/
gtk_entry_set_text( GTK_ENTRY (kalkulator->entry),
kalkulator->value1->str );
kalkulator->is_value1 = TRUE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = TRUE;
}
g_free( s_result );
kalkulator->prev_button = 14;
#ifdef DEBUG
info(kalkulator,"koniec funkcji =");
#endif
}
// obsługa zdarzeń emitowanych podczas zamykania programu
static gboolean delete_event( GtkWidget *widget,
GdkEvent *event,
gpointer data )
{
g_print("delete_event: FALSE\n");
return FALSE;
}
static void destroy( GtkWidget *widget, gpointer pkalkulator )
{
g_print("destroy: gtk_main_quit | g_free\n");
Kalkulator *kalkulator = pkalkulator;
gtk_main_quit ();
if (kalkulator->is_value1 == TRUE)
{
g_string_free (kalkulator->value1,TRUE);
}
if (kalkulator->is_value2 == TRUE)
{
g_string_free (kalkulator->value2,TRUE);
}
g_free ( kalkulator );
}
int main( int argc, char *argv[] )
{
Kalkulator *kalkulator;
kalkulator = g_malloc ( sizeof( Kalkulator ) );
kalkulator->operation = -1;
kalkulator->prev_operation = -1;
kalkulator->is_value1 = FALSE;
kalkulator->is_value2 = FALSE;
kalkulator->is_result = FALSE;
kalkulator->prev_button = -1;
GtkWidget *tmp;
gtk_init (&argc, &argv);
// okno i jego podstawowe właściwości
kalkulator->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (kalkulator->window), "delete_event",
G_CALLBACK (delete_event), NULL);
g_signal_connect (G_OBJECT (kalkulator->window), "destroy",
G_CALLBACK (destroy), kalkulator);
gtk_container_set_border_width (GTK_CONTAINER (kalkulator->window), 5);
gtk_window_set_title (GTK_WINDOW (kalkulator->window), "kalkulator");
gtk_window_set_resizable ( GTK_WINDOW (kalkulator->window), FALSE );
// tabela
/*
0/0 - 1 - 2 - 3 - 4 - 5 - 6
1 | _ | _ | _ | _ | _ | _ |
2 | _ | _ | _ | _ | _ | _ |
3 | _ | _ | _ | _ | _ | _ |
4 | _ | _ | _ | _ | _ | _ |
*/
kalkulator->table = gtk_table_new (4, 6, TRUE);
gtk_container_add ( GTK_CONTAINER (kalkulator->window),
kalkulator->table);
// entry
kalkulator->entry = gtk_entry_new ( );
gtk_entry_set_alignment ( GTK_ENTRY( kalkulator->entry ), 1);
gtk_entry_set_editable ( GTK_ENTRY( kalkulator->entry ), FALSE);
// 0,6 - szerokość od 0 do 6
// 0,1 - wysokość od 0 do 1
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
kalkulator->entry, 0, 6, 0, 1 );
// przyciski 1
// 0,1 - szerokość od 0 do 1
// 1,2 - wysokość od 1 do 2
tmp = gtk_button_new_with_label ("1");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_1_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 0, 1, 1, 2);
// przyciski 2
tmp = gtk_button_new_with_label ("2");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_2_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 1, 2, 1, 2);
// przyciski 3
tmp = gtk_button_new_with_label ("3");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_3_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 2, 3, 1, 2);
// drugi wiersz
// przyciski 4
tmp = gtk_button_new_with_label ("4");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_4_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 0, 1, 2, 3);
// przyciski 5
tmp = gtk_button_new_with_label ("5");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_5_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 1, 2, 2, 3);
// przyciski 6
tmp = gtk_button_new_with_label ("6");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_6_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 2, 3, 2, 3);
// trzeci wiersz
// przyciski 7
tmp = gtk_button_new_with_label ("7");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_7_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 0, 1, 3, 4);
// przyciski 8
tmp = gtk_button_new_with_label ("8");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_8_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 1, 2, 3, 4);
// przyciski 9
tmp = gtk_button_new_with_label ("9");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_9_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 2, 3, 3, 4);
// przyciski 0
tmp = gtk_button_new_with_label ("0");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_0_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 3, 4, 3, 4);
// przyciski <-
tmp = gtk_button_new_with_label ("<-");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_back_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 3, 4, 2, 3);
// przyciski C
tmp = gtk_button_new_with_label ("C");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_clear_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 3, 4, 1, 2);
// przyciski +
tmp = gtk_button_new_with_label ("+");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_add_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 4, 5, 1, 2);
// przyciski -
tmp = gtk_button_new_with_label ("-");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_sub_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 4, 5, 2, 3);
// przyciski *
tmp = gtk_button_new_with_label ("*");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_multi_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 4, 5, 3, 4);
// przyciski /
tmp = gtk_button_new_with_label ("/");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_div_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 5, 6, 1, 2);
// przyciski =
tmp = gtk_button_new_with_label ("=");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_result_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 5, 6, 3, 4);
// przyciski ,
tmp = gtk_button_new_with_label (",");
g_signal_connect (G_OBJECT (tmp), "clicked",
G_CALLBACK (btn_comma_clicked), kalkulator);
gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
tmp, 5, 6, 2, 3);
gtk_widget_show_all (kalkulator->window);
#ifdef DEBUG
info(kalkulator,"przed gtk_main");
#endif
gtk_main ();
return 0;
}