/* Copyright (c) 2005-2008 DN-Systems GmbH http://rfdump.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

/* $Header: /cvs/rfdump/src/main.c,v 1.61 2008/06/11 20:29:49 doj Exp $ */

#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <gtk/gtk.h>
#include <glib/gstdio.h>

#include "main.h"
#include "rfid.h"
#include "tagtypes.h"
#include "tools.h"
#include "xml.h"

/************************************************************/
/* global variables */

int use_cookies;
gchar *cookie_id;

static struct RFIDTagType *tagTypes[100];

int fd=-1;
struct RFIDTag tag;

GtkWidget *entryTagID;
GtkWidget *entryManufacturer;
GtkWidget *entryTagType;
static GtkWidget *treeview1;
static GtkWidget *statusbar;
GtkWidget *windowRFDump, *windowAbout, *windowPreferences, *windowMiFareCrack;
GtkWidget *keya, *keyb;
GtkTreeModel *model;
GtkBuilder *builder;
GKeyFile *prefs;
char *PREFS_FN;

static int statusbar_context_id;
static char *load_xml_on_init;
static int setup_reader_on_init;

enum
  {
    COL_ADR = 0,
    COL_0,
    COL_1,
    COL_2,
    COL_3,
    COL_ASCII,
    NUM_COLS
  };

#define NUM_HEX_COLS (COL_3-COL_0+1)

char *builder_file[] = {
  "rfdump.ui",
  "/usr/share/rfdump/rfdump.ui",
  "/usr/local/share/rfdump/rfdump.ui",
};

/***************************************************************/
/* functions */

void statusbar_set(char *str)
{
  gtk_statusbar_pop(GTK_STATUSBAR(statusbar), statusbar_context_id);
  gtk_statusbar_push(GTK_STATUSBAR(statusbar), statusbar_context_id, str);
}

void error_dialog(char *message)
{
  GtkWidget *d=gtk_message_dialog_new(GTK_WINDOW(windowRFDump),
				      GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, /* GtkDialogFlags */
                                      GTK_MESSAGE_ERROR,
                                      GTK_BUTTONS_OK,
				      "%s", message);
  g_assert(d);
  /*gint result =*/ gtk_dialog_run(GTK_DIALOG(d));
#if 0
  switch (result)
    {
      case GTK_RESPONSE_ACCEPT:
         do_application_specific_something ();
         break;
      default:
         do_nothing_since_dialog_was_cancelled ();
         break;
    }
#endif
  gtk_widget_destroy(d);
}

void info_dialog(char *message)
{
  GtkWidget *d=gtk_message_dialog_new(GTK_WINDOW(windowRFDump),
				      GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, /* GtkDialogFlags */
                                      GTK_MESSAGE_INFO,
                                      GTK_BUTTONS_OK,
				      "%s", message);
  g_assert(d);
  gtk_dialog_run(GTK_DIALOG(d));
  gtk_widget_destroy(d);
}

int getCharValue(char c)
{
  switch (c)
    {
    case '0' : return 0;
    case '1' : return 1;
    case '2' : return 2;
    case '3' : return 3;
    case '4' : return 4;
    case '5' : return 5;
    case '6' : return 6;
    case '7' : return 7;
    case '8' : return 8;
    case '9' : return 9;
    case 'A' : case 'a' : return 10;
    case 'B' : case 'b' : return 11;
    case 'C' : case 'c' : return 12;
    case 'D' : case 'd' : return 13;
    case 'E' : case 'e' : return 14;
    case 'F' : case 'f' : return 15;
    }
  return 0;
}

char dec2hex(const int i)
{
  switch(i)
    {
    case 0: return '0';
    case 1: return '1';
    case 2: return '2';
    case 3: return '3';
    case 4: return '4';
    case 5: return '5';
    case 6: return '6';
    case 7: return '7';
    case 8: return '8';
    case 9: return '9';
    case 10: return 'A';
    case 11: return 'B';
    case 12: return 'C';
    case 13: return 'D';
    case 14: return 'E';
    case 15: return 'F';
    }
  return '!';
}

void updateGUI(int adr, gchar *new_text)
{
  //fprintf(stderr, "updateGUI(%i, %s)\n", adr, new_text);

  const int row_number = (int)adr/NUM_HEX_COLS;
  const int column_number = (adr%NUM_HEX_COLS)+1;

  char path_string[255];
  SNPRINTF(path_string, sizeof(path_string), "%d", row_number);

  GtkTreePath *path = gtk_tree_path_new_from_string(path_string);

  GtkTreeIter iter;
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_list_store_set((GtkListStore*)model, &iter, column_number, new_text, -1);

  /* update ASCII */
  gchar *ascii;
  gtk_tree_model_get(model, &iter, COL_ASCII, &ascii, -1);

  const int len=strlen(new_text)/2;
  int i;
  for (i=0; i<len; i++)
    {
      int asciiCode;
      if(new_text[2*i]=='-' && new_text[2*i+1]=='-')
	asciiCode='-';
      else
	asciiCode=getCharValue(new_text[2*i])*16 + getCharValue(new_text[2*i+1]);

      ascii[(column_number-1)*len+i] = (isprint(asciiCode) && asciiCode<128)?asciiCode:'.';
    }
  ascii[i]=0;

  gtk_list_store_set((GtkListStore*)model, &iter, COL_ASCII, ascii, -1);
}

static void cell_edited_callback (GtkCellRendererText *cell, gchar *path_string, gchar *new_text, gpointer user_data)
{
  //fprintf(stderr, "CELL EDITED(%s):%s| ", path_string, new_text);

  /* get column */
  guint column_number = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cell), "my_column_num"));
  //fprintf(stderr, "COLUMN:%d ", column_number);

  //fprintf(stderr, "cell_edited_callback(%s, %s):col=%i:len=%i\n", path_string, new_text, column_number, strlen(new_text));

#if 0
  g_object_set(cell,
	       "cell-background", "LightSalmon",
	       "cell-background-set", TRUE,
	       NULL);
#endif

  /* do padding / cropping for ASCII field if necessary */
  if (column_number == COL_ASCII)
    {
      if (strlen(new_text)>NUM_HEX_COLS*tag.pageSize)
	{
	  new_text[NUM_HEX_COLS*tag.pageSize] = '\0';
	}
      else if ((strlen(new_text)<NUM_HEX_COLS*tag.pageSize))
	{
	  int i;

	  gchar *tmp_new_text = (gchar*)g_malloc((NUM_HEX_COLS*tag.pageSize)+1); /* bug: this memory is lost */
	  strcpy(tmp_new_text, new_text);

	  for (i=0; i<(NUM_HEX_COLS*tag.pageSize)-strlen(new_text); i++)
	    strcat(tmp_new_text," ");

	  new_text = tmp_new_text;
	}
    }
  /* do padding / cropping for HEX field if necessary */
  else if (column_number>=COL_0 && column_number<COL_ASCII)
    {
      int i;
      if (strlen(new_text)>2*tag.pageSize)
	{
	  new_text[2*tag.pageSize] = '\0';
	}
      else if ((strlen(new_text)<2*tag.pageSize))
	{
	  gchar *tmp_new_text = (gchar*)g_malloc((2*tag.pageSize)+1);	/* bug: this memory is lost */
	  strcpy(tmp_new_text, new_text);

	  for (i=0; i<(2*tag.pageSize)-strlen(new_text); i++)
	    strcat(tmp_new_text,"0");

	  new_text = tmp_new_text;
	}

      for(i=0; i<strlen(new_text); ++i)
	{
	  new_text[i]=toupper(new_text[i]);
	  if(!isxdigit(new_text[i]))
	    new_text[i]='0';
	}
    }
  else
    error_dialog("A: unknown table col");

  /* get row */
  GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
  gint *indices = gtk_tree_path_get_indices(path);
  const int row_number = indices[0];
  //fprintf(stderr, "ROW:%d ", row_number);

  /* update model */
  GtkTreeIter iter;
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_list_store_set((GtkListStore*)model, &iter, column_number, g_strdup(new_text), -1);

  /* write data to RFID tag */

  /* HEX editor */
  if (column_number>=COL_0 && column_number<COL_ASCII)
    {
      rfidWritePage(fd, row_number*NUM_HEX_COLS + column_number-1, new_text);

      /* update ASCII */
      gchar *ascii;
      gtk_tree_model_get(model, &iter, COL_ASCII, &ascii, -1);

      const unsigned len=strlen(new_text)/2;
      int i;
      for (i=0; i<len; i++)
	{
	  const int asciiCode = getCharValue(new_text[2*i])*16 + getCharValue(new_text[2*i+1]);
	  ascii[(column_number-1)*len+i] = (isprint(asciiCode) && asciiCode<128)?asciiCode:'.';
	}

      gtk_list_store_set((GtkListStore*)model, &iter, COL_ASCII, ascii, -1);
    }
  /* ASCII editor */
  else if (column_number == COL_ASCII)
    {
      const int pageLen=tag.pageSize*2*NUM_HEX_COLS + 10;
      char *page=g_malloc(pageLen);
      int hexCol = 1;
      unsigned char *new_text_u=(unsigned char*)new_text;

      page[0] = '\0';

      const int S=strlen(new_text);
      int i;
      for (i=0; i<S; i++)
	{
	  char pageByte[sizeof(int64_t)+1];
	  SNPRINTF(pageByte, sizeof(pageByte), "%02X", new_text_u[i]);
	  strcat(page, pageByte);

	  if ((i+1)%tag.pageSize == 0)
	    {
	      gchar *hexValue = g_strdup(page);

	      rfidWritePage(fd, (row_number*NUM_HEX_COLS)+(int)(i/tag.pageSize), page);
	      page[0] = '\0';

	      /* update HEX */
	      //fprintf(stderr, "NEW VALUE:%s ", hexValue);

	      gtk_list_store_set((GtkListStore*)model, &iter, hexCol, hexValue, -1);
	      hexCol++;
	    }
	}
      g_free(page);
    }
  else
    error_dialog("B: unknown table col");

  //fprintf(stderr, " <-CELL EDITED\n");
}

static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  stop_scan();
  gtk_main_quit();
  return FALSE;
}

static gboolean null_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  return TRUE;
}

int scan_run;
void stop_scan()
{
  statusbar_set("");
  scan_run=FALSE;
}

void setup_table()
{
  gtk_list_store_clear((GtkListStore*)model);

  const int k_max=((tag.memSize-1)/NUM_HEX_COLS)+1;
  int k;
  for (k=0; k<k_max; k++)
    {
      char *row[NUM_COLS];
      row[0] = g_strdup_printf("%02X",(int)k*NUM_HEX_COLS);

      row[COL_ASCII] = (char*)g_malloc(255+1);
      *(row[COL_ASCII]) = '\0';

      int i;
      for (i=0; i<NUM_HEX_COLS; i++)
	{
	  int adr = k*NUM_HEX_COLS+i;

	  if (tag.mem[adr] != NULL)
	    {
	      row[i+1] = g_strdup(tag.mem[adr]);
	      strcat(row[COL_ASCII], tag.mem[adr]);
	    }
	  else
	    {
	      row[i+1] = g_strdup("--");
	      int j;
	      for(j=0; j<tag.pageSize; ++j)
		strcat(row[COL_ASCII], "--");
	    }
	}

      const int len=strlen(row[COL_ASCII])/2;
      for (i=0; i<len; i++)
	{
	  int asciiCode;
	  if(row[COL_ASCII][2*i]=='-' && row[COL_ASCII][2*i+1]=='-')
	    asciiCode='-';
	  else
	    asciiCode = getCharValue(row[COL_ASCII][2*i])*16 + getCharValue(row[COL_ASCII][2*i+1]);
	  row[COL_ASCII][i] = (isprint(asciiCode) && asciiCode<128)?asciiCode:'.';
	}
      row[COL_ASCII][i] = '\0';

      /* append an empty row to the list store, iter will point to the new row */
      GtkTreeIter iter;
      gtk_list_store_append((GtkListStore*)model, &iter);

      /* fill fields with data */
      gtk_list_store_set((GtkListStore*)model, &iter, 0, row[0], 1, row[1], 2, row[2], 3, row[3], 4, row[4], 5, row[5], -1);
    }

  gtk_entry_set_text((GtkEntry*)entryTagID, tag.tagID);
  gtk_entry_set_text((GtkEntry*)entryTagType, tag.tagTypeName);
  gtk_entry_set_text((GtkEntry*)entryManufacturer, tag.tagManufacturerName);
}

static gint scan_callback(gpointer data)
{
  if(!scan_run)
    return FALSE;

  /* spinning statusbar */
  {
    char *s;
    static int c=0;
    switch(++c)
      {
      default: c=0; /* no break */
      case 0: s="Scanning for tags --"; break;
      case 1: s="Scanning for tags \\"; break;
      case 2: s="Scanning for tags |"; break;
      case 3: s="Scanning for tags /"; break;
      }
    statusbar_set(s);
  }

  char line[255];
  if(rfidSelect(fd, line, sizeof(line)) < 0)
    return TRUE;

  if(line[0] == 'N')		/* no tag found */
    return TRUE;

  const int tagTypeIdx = getTagType(line, tagTypes);
  if(tagTypeIdx < 0)
    {
      char buf[256];
      SNPRINTF(buf, sizeof(buf), "unknown tag: %s", line);
      statusbar_set(buf);
      return TRUE;
    }
  else
    fprintf(stderr, "TAG: %s\n", line);

  /* get tag info */
  rfidGetTagID(line, tag.tagID);
  strcpy(tag.tagTypeName, tagTypes[tagTypeIdx]->tagTypeName);
  strcpy(tag.tagManufacturerName, tagTypes[tagTypeIdx]->tagManufacturerName);
  tag.memSize = tagTypes[tagTypeIdx]->memSize;
  tag.pageSize = tagTypes[tagTypeIdx]->pageSize;

  gtk_entry_set_text((GtkEntry*)entryTagID, tag.tagID);
  gtk_entry_set_text((GtkEntry*)entryTagType, tag.tagTypeName);
  gtk_entry_set_text((GtkEntry*)entryManufacturer, tag.tagManufacturerName);

  gtk_list_store_clear((GtkListStore*)model);
  poll_gtk();

  /* read memory */
  if(rfidReadAllPages(fd, &tag) < 0)
    {
      fprintf(stderr, "could not read all pages\n");
      stop_scan();
      return FALSE;
    }

  if(tag.memSize <= 0)
    {
      fprintf(stderr, "read all pages resulted in 0 pages read\n");
      stop_scan();
      return FALSE;
    }

  setup_table();

  /* set cookie */
  if (use_cookies)
    {
      char page[255];
      if(rfidReadPage(fd, tag.memSize-2, page, sizeof(page)) < 0)
	return TRUE;

#if 0
      int i;
      for (i=0; i<strlen(cookie_id); i++)
	{
	  cookie_id[i] = toupper(cookie_id[i]);
	}
#endif
      if (!strcmp(page, cookie_id))
	{
	  fprintf(stderr, "COOKIE FOUND\n");

	  int counter = rfidIncrementCookie(fd, &tag);
	  if(counter < 0)
	    return TRUE;
	  SNPRINTF(page, sizeof(page), "%08d", counter);
	  updateGUI(tag.memSize-1, page);
	  //gtk_entry_set_text(GTK_ENTRY(entryCounter), page);
	}
      else
	{
	  fprintf(stderr, "NO COOKIE FOUND - INITIALIZING\n");

	  if(rfidInitCookie(fd, &tag, cookie_id) < 0)
	    return TRUE;
	  updateGUI(tag.memSize-2, cookie_id);
	  updateGUI(tag.memSize-1, "00000000");
	}
    }

  stop_scan();
  return FALSE;
}

void start_scan()
{
  if(scan_run)
    return;
  scan_run=TRUE;
  statusbar_set("Scanning for tags --");
  g_timeout_add(1000, scan_callback, NULL);
}

gint init_reader(gpointer data)
{
  g_assert(data);
  init_reader_arg *arg=(init_reader_arg*)data;

  /* read about tag types defined in rfd_types.xml */
  const int noTagTypes = readTagTypes(tagTypes, sizeof_array(tagTypes));
  if(noTagTypes < 0)
    {
      error_dialog("Error while reading rfd_types.xml. See stderr for details.");
      exit(1);
    }
  if(noTagTypes == 0)
    {
      error_dialog("Found no tag types in rfd_types.xml");
      exit(1);
    }

  /* init reader */
  if(! arg->readerdevice)
    {
      error_dialog("Serial port not set. Please set the serial port in the preferences or use the -p argument and restart rfdump.");
      return FALSE;
    }

  int br=9600;
  if(arg->baudrate) br=atoi(arg->baudrate);
  fd = rfidConnectReader(arg->readerdevice, br, arg->binarymode);
  if(fd < 0)
    {
      error_dialog("Could not initialize RFID reader.");
      goto finished;
    }
  if(rfidReset(fd) < 0)
    {
      error_dialog("Could not reset RFID reader.");
      goto finished;
    }
  if(!rfidReaderSetup)
    {
      error_dialog("Did not detect your RFID reader.");
      goto finished;
    }
  if(setup_reader_on_init)
    on_set_to_defaults1_activate(NULL, NULL);

  if(load_xml_on_init)
    {
      xmlReadTag(&tag, load_xml_on_init);
      setup_table();
    }
  else
    start_scan();

 finished:
  return FALSE;
}

void read_memory_tick(int adr)
{
  char s[32];
  SNPRINTF(s, sizeof(s), "Reading Page %02X", adr);
  statusbar_set(s);
  poll_gtk();
}

void write_memory_tick(int adr)
{
  char s[32];
  SNPRINTF(s, sizeof(s), "Writing Page %02X", adr);
  statusbar_set(s);
  poll_gtk();
}

void poll_gtk()
{
  while(gtk_events_pending())
    gtk_main_iteration();
}

void writePreferences()
{
  if(prefs && PREFS_FN)
    {
      gsize len;
      char *s=g_key_file_to_data(prefs, &len, NULL);
      if(s && len>0)
	{
	  FILE *f=fopen(PREFS_FN, "w");
	  if(f)
	    {
	      if(fwrite(s, 1, len, f) < len)
		fprintf(stderr, "could not write preferences: %s\n", strerror(errno));
	      fclose(f);
	    }
	  else
	    fprintf(stderr, "could not open preferences to write %s : %s\n", PREFS_FN, strerror(errno));
	  g_free(s);
	}
    }
}

/****************************************************************/

static void help()
{
  printf("\nusage: rfdump [--setupreader] [-p /dev/tty...] [file.xml]\n");
  exit(1);
}

static void cleanup()
{
  g_key_file_free(prefs);
  g_free(PREFS_FN);
}

static void crash_handler(int sig)
{
  fprintf(stderr, "received signal %s\n", strsignal(sig));
  serial_cleanup();
  abort();
}

static void int_handler(int sig)
{
  fprintf(stderr, "received signal %s\n", strsignal(sig));
  exit(1);
}

int main (int argc, char *argv[])
{
  init_reader_arg arg;

#ifdef ENABLE_NLS
  bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);
#endif

  gtk_init (&argc, &argv);

#if 0
  /* check if gtk library will work */
  gchar *gtk_err = gtk_check_version(GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
  if(gtk_err)
    {
      fprintf(stderr, "%s\n", gtk_err);
      exit(EXIT_FAILURE);
    }
  else
    {
      printf("compiled with gtk+-%i.%i.%i, dynamically linked to gtk+-%i.%i.%i\n",
	     GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
	     gtk_major_version, gtk_minor_version, gtk_micro_version
	     );
    }
#endif

  /* read preferences */
  const char *config_dir=g_get_user_config_dir();
  g_mkdir(config_dir, 0700);
  PREFS_FN=g_strdup_printf("%s/%s", config_dir, "rfdump.ini");
  g_assert(PREFS_FN);
  prefs=g_key_file_new();
  g_assert(prefs);
  GError *err=0;
  if(!g_key_file_load_from_file(prefs, PREFS_FN, G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &err))
    {
      fprintf(stderr, "could not load preferences %s : %s\n", PREFS_FN, err?err->message:"");
      g_key_file_set_comment(prefs, NULL, NULL, " Preferences for RFDump " VERSION, &err);
    }
  else
    fprintf(stderr, "using preferences in %s\n", PREFS_FN);

  cookie_id=g_key_file_get_value(prefs, PREFS_GROUP, PREFS_COOKIE, NULL);
  if(!cookie_id || strlen(cookie_id)<1)
    cookie_id="DEADFACE";

  {
    err=0;
    gboolean b=g_key_file_get_boolean(prefs, PREFS_GROUP, PREFS_COOKIE_ACTIVE, &err);
    if(!err)
      use_cookies=b;
  }

  arg.readerdevice=g_key_file_get_value(prefs, PREFS_GROUP, PREFS_ACG_SERIALPORT, NULL);
  arg.baudrate=g_key_file_get_value(prefs, PREFS_GROUP, PREFS_ACG_BAUDRATE, NULL);
  arg.binarymode=g_key_file_get_boolean(prefs, PREFS_GROUP, PREFS_ACG_BINARYMODE, NULL);

  /* parse cmd line */
  int a;
  for(a=1; a<argc; ++a)
    {
      if(!strcmp(argv[a], "-setupreader") || !strcmp(argv[a], "--setupreader"))
	setup_reader_on_init=TRUE;
      else if(!strcmp(argv[a], "-p") && a+1<argc)
	arg.readerdevice=argv[++a];
      else
	{
	  const int len=strlen(argv[a]);
	  if(len>4 && argv[a][len-4]=='.' && argv[a][len-3]=='x' && argv[a][len-2]=='m' && argv[a][len-1]=='l')
	    load_xml_on_init=argv[a];
	  else
	    help();
	}
    }

  /* signal handlers */
  atexit(cleanup);
  signal(SIGSEGV, crash_handler);
  signal(SIGINT, int_handler);


  /* init gtkbuilder */
  char *fn=0;
  int i;
  for(i=0; i<sizeof_array(builder_file); ++i)
    if(access(builder_file[i], R_OK) == 0)
      {
	fn=builder_file[i];
	break;
      }

  if(!fn)
    {
      fprintf(stderr, "could not find %s\n", builder_file[0]);
      exit(1);
    }

  fprintf(stderr, "using %s\n", fn);
  GError* error = NULL;
  builder = gtk_builder_new ();
  if (!gtk_builder_add_from_file (builder, fn, &error))
    {
      g_warning ("Couldn't load builder file: %s", error->message);
      g_error_free (error);
    }
  gtk_builder_connect_signals(builder, NULL);

  keya = GTK_WIDGET (gtk_builder_get_object (builder, "keya")); g_assert(keya);
  keyb = GTK_WIDGET (gtk_builder_get_object (builder, "keyb")); g_assert(keyb);
  treeview1 = GTK_WIDGET (gtk_builder_get_object (builder, "treeview1")); g_assert(treeview1);
  entryTagID= GTK_WIDGET (gtk_builder_get_object (builder, "entryTagID")); g_assert(entryTagID);
  entryTagType= GTK_WIDGET (gtk_builder_get_object (builder, "entryTagType")); g_assert(entryTagType);
  entryManufacturer= GTK_WIDGET (gtk_builder_get_object (builder, "entryManufacturer")); g_assert(entryManufacturer);
  statusbar= GTK_WIDGET (gtk_builder_get_object (builder, "statusbar")); g_assert(statusbar);
  statusbar_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "RFDump Statusbar");

  windowRFDump = GTK_WIDGET (gtk_builder_get_object (builder, "windowRFDump")); g_assert(windowRFDump);
  g_signal_connect(G_OBJECT(windowRFDump), "delete_event", G_CALLBACK(delete_event), NULL);

  windowAbout = GTK_WIDGET (gtk_builder_get_object (builder, "windowAbout")); g_assert(windowAbout); //gtk_widget_hide(windowAbout);
  g_signal_connect(G_OBJECT(windowAbout), "delete_event", G_CALLBACK(null_event), NULL);

  windowPreferences = GTK_WIDGET (gtk_builder_get_object (builder, "windowPreferences")); g_assert(windowPreferences); //gtk_widget_hide(windowPreferences);
  g_signal_connect(G_OBJECT(windowPreferences), "delete_event", G_CALLBACK(null_event), NULL);

  windowMiFareCrack = GTK_WIDGET (gtk_builder_get_object (builder, "windowMiFareCrack")); g_assert(windowMiFareCrack); //gtk_widget_hide(windowMiFareCrack);
  g_signal_connect(G_OBJECT(windowMiFareCrack), "delete_event", G_CALLBACK(null_event), NULL);

  /* initialize reader after 1s */
  statusbar_set("Initializing Reader");
  g_timeout_add(1000, init_reader, &arg);

  /* create list view and model */

  GtkListStore *liststore;
  /*GtkTreeIter   iter;*/

  liststore = gtk_list_store_new(NUM_COLS,
				 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
				 G_TYPE_STRING);

  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;

  /* --- Column #1 (Adr) --- */
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(col, "Adr");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview1), col);
  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);
  g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COL_ADR));
  gtk_tree_view_column_add_attribute(col, renderer, "text", COL_ADR);
  g_signal_connect(renderer, "edited", (GCallback)cell_edited_callback, liststore);

  /* --- Columns #2 - #5 --- (Hex) */
  const char* headers[NUM_HEX_COLS] = {"HEX", "", "", ""};
  int colNo;
  for (colNo=1; colNo<=NUM_HEX_COLS; colNo++)
    {
      col = gtk_tree_view_column_new();
      gtk_tree_view_column_set_title(col, headers[colNo-1]);

      /* pack tree view column into tree view */
      gtk_tree_view_append_column(GTK_TREE_VIEW(treeview1), col);
      renderer = gtk_cell_renderer_text_new();

      /* pack cell renderer into tree view column */
      gtk_tree_view_column_pack_start(col, renderer, TRUE);

      /* set 'cell-background' property of the cell renderer */
      g_object_set(renderer,
		   "cell-background", "LightGray",
		   "cell-background-set", TRUE,
		   NULL);
      g_object_set(renderer, "editable", TRUE, NULL);
      g_object_set(renderer, "family", "Monospace", NULL);
      g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(colNo));
      gtk_tree_view_column_add_attribute(col, renderer, "text", colNo);
      g_signal_connect(renderer, "edited", (GCallback)cell_edited_callback, liststore);
    }

  /* --- Column #6 (ASCII) --- */
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(col, "ASCII");

  /* pack tree view column into tree view */
  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview1), col);
  renderer = gtk_cell_renderer_text_new();

  /* pack cell renderer into tree view column */
  gtk_tree_view_column_pack_start(col, renderer, TRUE);
  g_object_set(renderer, "editable", TRUE, NULL);
  g_object_set(renderer, "family", "Monospace", NULL);
  g_object_set_data(G_OBJECT(renderer), "my_column_num", GUINT_TO_POINTER(COL_ASCII));
  gtk_tree_view_column_add_attribute(col, renderer, "text", COL_ASCII);
  g_signal_connect(renderer, "edited", (GCallback)cell_edited_callback, liststore);

  model = GTK_TREE_MODEL(liststore);
  g_assert(model);
  gtk_tree_view_set_model(GTK_TREE_VIEW(treeview1), GTK_TREE_MODEL(liststore));

  g_object_unref(liststore);
  /* Now the model will be destroyed when the tree view is destroyed */

  gtk_main();
  return EXIT_SUCCESS;
}
