/* $Id$ */

/* Copyright (C) 2001 The gtkmm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "demowindow.h"
#include "gtkmm/main.h"
#include "gtkmm/cellrenderertext.h"
#include "gtkmm/treeviewcolumn.h"
#include "textwidget.h"
#include "demos.h"
#include <vector>
#include <cctype>
#include <cerrno>
#include <stdio.h>

#include <cstring>

using std::isspace;
using std::strlen;

#include "demo-common.h"

#ifdef NEED_FLOCKFILE_PROTO
extern "C" void flockfile (FILE *);
#endif
#ifdef NEED_FUNLOCKFILE_PROTO
extern "C" void funlockfile (FILE *);
#endif
#ifdef NEED_GETC_UNLOCKED_PROTO
extern "C" int getc_unlocked (FILE *);
#endif

namespace
{

struct DemoColumns : public Gtk::TreeModelColumnRecord
{
  Gtk::TreeModelColumn<Glib::ustring> title;
  Gtk::TreeModelColumn<Glib::ustring> filename;
  Gtk::TreeModelColumn<type_slotDo>   slot;
  Gtk::TreeModelColumn<bool>          italic;

  DemoColumns() { add(title); add(filename); add(slot); add(italic); }
};

// Singleton accessor function.
const DemoColumns& demo_columns()
{
  static DemoColumns column_record;
  return column_record;
}

} // anonymous namespace


DemoWindow::DemoWindow()
: m_TextWidget_Info(false),
  m_TextWidget_Source(true)
{
  m_pWindow_Example = 0;

  set_title("gtkmm Code Demos");

  add(m_HBox);

  //Tree:
  m_refTreeStore = Gtk::TreeStore::create(demo_columns());
  m_TreeView.set_model(m_refTreeStore);

  m_refTreeSelection = m_TreeView.get_selection();
  m_refTreeSelection->set_mode(Gtk::SELECTION_BROWSE);
  m_refTreeSelection->set_select_function( sigc::ptr_fun(&DemoWindow::select_function) );

  m_TreeView.set_size_request(200, -1);

  fill_tree();

  m_HBox.pack_start(m_TreeView, Gtk::PACK_SHRINK);

  //Notebook:
  m_Notebook.append_page(m_TextWidget_Info, "_Info", true);  //true = use mnemonic.
  m_Notebook.append_page(m_TextWidget_Source, "_Source", true);  //true = use mnemonic.
  m_HBox.pack_start(m_Notebook);

  set_default_size (600, 400);

  load_file (testgtk_demos[0].filename);
  show_all();
}

void DemoWindow::fill_tree()
{
  const DemoColumns& columns = demo_columns();

  /* this code only supports 1 level of children. If we
   * want more we probably have to use a recursing function.
   */
  for(Demo* d = testgtk_demos; d && d->title; ++d)
  {
    Gtk::TreeRow row = *(m_refTreeStore->append());

    row[columns.title]    = d->title;
    row[columns.filename] = d->filename;
    row[columns.slot]     = d->slot;
    row[columns.italic]   = false;

    for(Demo* child = d->children; child && child->title; ++child)
    {
      Gtk::TreeRow child_row = *(m_refTreeStore->append(row.children()));

      child_row[columns.title]    = child->title;
      child_row[columns.filename] = child->filename;
      child_row[columns.slot]     = child->slot;
      child_row[columns.italic]   = false;
    }
  }

  Gtk::CellRendererText* pCell = Gtk::manage(new Gtk::CellRendererText());
  pCell->property_style() = Pango::STYLE_ITALIC;

  Gtk::TreeViewColumn* pColumn = new Gtk::TreeViewColumn("Widget (double click for demo)", *pCell);
  pColumn->add_attribute(pCell->property_text(), columns.title);
  pColumn->add_attribute(pCell->property_style_set(), columns.italic);

  m_TreeView.append_column(*pColumn);

  m_refTreeSelection->signal_changed().connect(sigc::mem_fun(*this, &DemoWindow::on_treeselection_changed));
  m_TreeView.signal_row_activated().connect(sigc::mem_fun(*this, &DemoWindow::on_treeview_row_activated));

  m_TreeView.expand_all();
}

DemoWindow::~DemoWindow()
{
  on_example_window_hide(); //delete the example window if there is one.
}

void DemoWindow::on_treeview_row_activated(const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn* /* model */)
{
  m_TreePath = path;

  if(m_pWindow_Example == 0) //Don't open a second window.
  {
    if(const Gtk::TreeModel::iterator iter = m_TreeView.get_model()->get_iter(m_TreePath))
    {
      Gtk::TreeModel::Row row = *iter;
      const DemoColumns& columns = demo_columns();

      const type_slotDo& slot = row[columns.slot];

      if(slot && (m_pWindow_Example = slot()))
      {
        row[columns.italic] = true;

        m_pWindow_Example->signal_hide().connect(sigc::mem_fun(*this, &DemoWindow::on_example_window_hide));
        m_pWindow_Example->show();
      }
    }
  }
}

bool DemoWindow::select_function(const Glib::RefPtr<Gtk::TreeModel>& model,
                                 const Gtk::TreeModel::Path& path, bool)
{
  const Gtk::TreeModel::iterator iter = model->get_iter(path);
  return iter->children().empty(); // only allow leaf nodes to be selected
}

void DemoWindow::on_treeselection_changed()
{
  if(const Gtk::TreeModel::iterator iter = m_refTreeSelection->get_selected())
  {
    const Glib::ustring filename = (*iter)[demo_columns().filename];

    load_file(Glib::filename_from_utf8(filename));
  }
}

bool DemoWindow::read_line (FILE *stream, GString *str)
{
  int n_read = 0;

#ifdef HAVE_FLOCKFILE
  flockfile (stream);
#endif

  g_string_truncate (str, 0);

  while (1)
    {
      int c;

#ifdef HAVE_GETC_UNLOCKED
      c = getc_unlocked (stream);
#endif //GLIBMM_PROPERTIES_ENABLED
      if (c == EOF)
	goto done;
      else
	n_read++;

      switch (c)
	{
	case '\r':
	case '\n':
	  {
#ifdef HAVE_GETC_UNLOCKED
	    int next_c = getc_unlocked (stream);
#endif //GLIBMM_PROPERTIES_ENABLED
	    if (!(next_c == EOF ||
		  (c == '\r' && next_c == '\n') ||
		  (c == '\n' && next_c == '\r')))
	      ungetc (next_c, stream);
	
	    goto done;
	  }
	default:
	  g_string_append_c (str, c);
	}
    }

 done:

#ifdef HAVE_FUNLOCKFILE
  funlockfile (stream);
#endif
  return n_read > 0;
}


void DemoWindow::load_file(const std::string& filename)
{
  if ( m_current_filename == filename )
  {
    return;
  }
  else
  {
    m_current_filename = filename;

    m_TextWidget_Info.wipe();
    m_TextWidget_Source.wipe();

    Glib::RefPtr<Gtk::TextBuffer> refBufferInfo = m_TextWidget_Info.get_buffer();
    Glib::RefPtr<Gtk::TextBuffer> refBufferSource = m_TextWidget_Source.get_buffer();

    FILE* file = fopen (filename.c_str(), "r");
    if (!file)
    {
      std::string installed = demo_find_file(filename);
      file = fopen (installed.c_str(), "r");
    }

    if (!file)
    {
      g_warning ("Cannot open %s: %s\n", filename.c_str(), g_strerror (errno));
      return;
    }

    GString *buffer = g_string_new (NULL);
    int state = 0;
    bool in_para = false;
    Gtk::TextBuffer::iterator start = refBufferInfo->get_iter_at_offset(0);
    while (read_line (file, buffer))
    {
      gchar *p = buffer->str;
      gchar *q = 0;
      gchar *r = 0;

      switch (state)
    	{
      	case 0:
      	  /* Reading title */
      	  while (*p == '/' || *p == '*' || isspace (*p))
      	    p++;

      	  r = p;

      	  while (*r != '/' && strlen (r))
      	    r++;
      	  if (strlen (r) > 0)
      	    p = r + 1;

      	  q = p + strlen (p);

      	  while (q > p && isspace (*(q - 1)))
      	    q--;

      	  if (q > p)
    	    {
    	      Gtk::TextBuffer::iterator end = start;

              const Glib::ustring strTemp (p, q);
    	      end = refBufferInfo->insert(end, strTemp);
    	      start = end;

    	      start.backward_chars(strTemp.length());
    	      refBufferInfo->apply_tag_by_name("title", start, end);

    	      start = end;
      	
    	      state++;
    	    }
    	    break;
  	
      	case 1:
      	  /* Reading body of info section */
      	  while (isspace (*p))
      	    p++;

      	  if (*p == '*' && *(p + 1) == '/')
    	    {
    	      start = refBufferSource->get_iter_at_offset(0);
    	      state++;
    	    }
      	  else
    	    {
    	      int len;
        	
    	      while (*p == '*' || isspace (*p))
    		      p++;

    	      len = strlen (p);
    	      while (isspace (*(p + len - 1)))
    		     len--;
        	
    	      if (len > 0)
                {
                  if (in_para)
                    {
                      start = refBufferInfo->insert(start, " ");
                    }

                  start = refBufferInfo->insert(start, Glib::ustring(p, p + len));
                  in_para = 1;
                }
              else
                {
                  start = refBufferInfo->insert(start, "\n");
                  in_para = 0;
                }
      	    }
      	  break;

      	case 2:
      	  /* Skipping blank lines */
      	  while (isspace (*p))
      	    p++;

      	  if (*p)
    	    {
    	      p = buffer->str;
    	      state++;
    	      /* Fall through */
    	    }
      	  else
      	    break;
        	
      	case 3:
      	  /* Reading program body */
      	  start = refBufferSource->insert(start, p);
      	  start = refBufferSource->insert(start, "\n");
      	  break;
      	}
     }

    m_TextWidget_Source.fontify();
  }
}


void DemoWindow::on_example_window_hide()
{
  if(m_pWindow_Example)
  {
    if(const Gtk::TreeModel::iterator iter = m_refTreeStore->get_iter(m_TreePath))
    {
      (*iter)[demo_columns().italic] = false;

      delete m_pWindow_Example;
      m_pWindow_Example = 0;
    }
  }
}

