Commit 693156f8 authored by Murray Cumming's avatar Murray Cumming Committed by Murray Cumming
Browse files

sql(): Handle TYPE_IMAGE fields.

2005-07-14  Murray Cumming  <murrayc.com>

        * glom/data_structure/field.cc: sql(): Handle TYPE_IMAGE
        fields.
        * glom/data_structure/glomconversions.cc:
        * glom/data_structure/glomconversions.h: Add
        get_escaped_binary_data() and parse_escaped_binary_data(),
        using a a copy of the PQunescapeBytea() function from
        Postgres, because gda_value_get_binary() does not unescape
        the data yet.
        * glom/utility_widgets/datawidget.cc: Connect to the
        LayoutWidgetField signals for the ImageGlom widget.
        * glom/utility_widgets/imageglom.cc:
        * glom/utility_widgets/imageglom.h: set_value():
        Use our copy of PQunescapeBytea().
        * glom/utility_widgets/layoutwidgetfield.cc:
        * glom/utility_widgets/layoutwidgetfield.h: Added
        get_has_original_data() for later optimisation.
parent da460f16
2005-07-14 Murray Cumming <murrayc.com>
* glom/data_structure/field.cc: sql(): Handle TYPE_IMAGE
fields.
* glom/data_structure/glomconversions.cc:
* glom/data_structure/glomconversions.h: Add
get_escaped_binary_data() and parse_escaped_binary_data(),
using a a copy of the PQunescapeBytea() function from
Postgres, because gda_value_get_binary() does not unescape
the data yet.
* glom/utility_widgets/datawidget.cc: Connect to the
LayoutWidgetField signals for the ImageGlom widget.
* glom/utility_widgets/imageglom.cc:
* glom/utility_widgets/imageglom.h: set_value():
Use our copy of PQunescapeBytea().
* glom/utility_widgets/layoutwidgetfield.cc:
* glom/utility_widgets/layoutwidgetfield.h: Added
get_has_original_data() for later optimisation.
2005-07-12 Murray Cumming <murrayc@murryac.com>
* glom/data_structure/field.h: Document the IMAGE type
......
......@@ -188,6 +188,7 @@ Glib::ustring Field::sql(const Gnome::Gda::Value& value) const
case(TYPE_DATE):
case(TYPE_TIME):
case(TYPE_NUMERIC):
case(TYPE_IMAGE):
{
return "NULL";
break;
......@@ -241,6 +242,17 @@ Glib::ustring Field::sql(const Gnome::Gda::Value& value) const
break;
}
case(TYPE_IMAGE):
{
if(value.get_value_type() == Gnome::Gda::VALUE_TYPE_BINARY)
{
long buffer_size = 0;
const gpointer buffer = value.get_binary(&buffer_size);
str = "'" + GlomConversions::get_escaped_binary_data((guint8*)buffer, buffer_size) + "'::bytea";
}
break;
}
default:
{
str = value.to_string();
......
......@@ -607,4 +607,138 @@ Gnome::Gda::Value GlomConversions::get_example_value(Field::glom_field_type fiel
}
Glib::ustring GlomConversions::get_escaped_binary_data(guint8* buffer, size_t buffer_size)
{
g_warning("debug: get_escaped_binary_data start");
g_warning("GlomConversions::get_escaped_binary_data: debug: buffer ");
for(int i = 0; i < 10; ++i)
g_warning("%02X (%c), ", (guint8)buffer[i], buffer[i]);
//TODO: Performance: Preallocate a string of the appropriate size.
//Use an output parameter instead of copying it during return.
Glib::ustring result;
if(buffer && buffer_size)
{
guint8* buffer_end = buffer + buffer_size;
char byte_as_octal[4]; //3 digits, and a null terminator
for(guint8* pos = buffer; pos < buffer_end; ++pos)
{
sprintf(byte_as_octal, "%03o", *pos); //Format as octal with 3 digits.
byte_as_octal[3] = 0;
// g_warning("byte=%d, as_hex=%s", *pos, byte_as_octal);
result += Glib::ustring("\\\\") + byte_as_octal;
}
}
g_warning("debug: get_escaped_binary_data end");
return result;
}
#define ISFIRSTOCTDIGIT(CH) ((CH) >= '0' && (CH) <= '3')
#define ISOCTDIGIT(CH) ((CH) >= '0' && (CH) <= '7')
#define OCTVAL(CH) ((CH) - '0')
Gnome::Gda::Value GlomConversions::parse_escaped_binary_data(const Glib::ustring& escaped_data)
{
//Hopefully we don't need to use this because Gda does it for us when we read a part of a "SELECT" result into a Gnome::Value.
//TODO: Performance
Gnome::Gda::Value result;
size_t buffer_binary_length = 0;
guchar* buffer_binary = Glom_PQunescapeBytea((guchar*)escaped_data.c_str(), &buffer_binary_length);
if(buffer_binary)
{
result.set(buffer_binary, buffer_binary_length);
free(buffer_binary);
}
return result;
}
unsigned char *
Glom_PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen)
{
size_t strtextlen,
buflen;
unsigned char *buffer,
*tmpbuf;
size_t i,
j;
if (strtext == NULL)
return NULL;
strtextlen = strlen((const char*)strtext);
/*
* Length of input is max length of output, but add one to avoid
* unportable malloc(0) if input is zero-length.
*/
buffer = (unsigned char *) malloc(strtextlen + 1);
if (buffer == NULL)
return NULL;
for (i = j = 0; i < strtextlen;)
{
switch (strtext[i])
{
case '\\':
i++;
if (strtext[i] == '\\')
buffer[j++] = strtext[i++];
else
{
if ((ISFIRSTOCTDIGIT(strtext[i])) &&
(ISOCTDIGIT(strtext[i + 1])) &&
(ISOCTDIGIT(strtext[i + 2])))
{
int byte;
byte = OCTVAL(strtext[i++]);
byte = (byte << 3) + OCTVAL(strtext[i++]);
byte = (byte << 3) + OCTVAL(strtext[i++]);
buffer[j++] = byte;
}
}
/*
* Note: if we see '\' followed by something that isn't a
* recognized escape sequence, we loop around having done
* nothing except advance i. Therefore the something will
* be emitted as ordinary data on the next cycle. Corner
* case: '\' at end of string will just be discarded.
*/
break;
default:
buffer[j++] = strtext[i++];
break;
}
}
buflen = j; /* buflen is the length of the dequoted
* data */
/* Shrink the buffer to be no larger than necessary */
/* +1 avoids unportable behavior when buflen==0 */
tmpbuf = (unsigned char*)realloc(buffer, buflen + 1);
/* It would only be a very brain-dead realloc that could fail, but... */
if (!tmpbuf)
{
free(buffer);
return NULL;
}
*retbuflen = buflen;
return tmpbuf;
}
......@@ -54,7 +54,15 @@ namespace GlomConversions
Gnome::Gda::Value get_empty_value(Field::glom_field_type field_type);
Gnome::Gda::Value get_example_value(Field::glom_field_type field_type);
Glib::ustring get_escaped_binary_data(guint8* buffer, size_t buffer_size);
Gnome::Gda::Value parse_escaped_binary_data(const Glib::ustring& escaped_data);
}
//Copied from Postgres's PQunescapeBytea() so I don't have the trouble of finding and linking to the
//postgres libraries directly, without the benefit of a pkg-config .pc file. murrayc.
unsigned char *
Glom_PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen);
#endif //GLOM_DATASTRUCTURE_GLOMCONVERSIONS_H
......@@ -43,6 +43,7 @@ DataWidget::DataWidget(const LayoutItem_Field& field, const Glib::ustring& table
set_layout_item(field.clone(), table_name); //takes ownership
Gtk::Widget* child = 0;
LayoutWidgetField* pFieldWidget = 0;
const Glib::ustring title = field.get_title_or_name();
if(glom_type == Field::TYPE_BOOLEAN)
{
......@@ -67,6 +68,7 @@ DataWidget::DataWidget(const LayoutItem_Field& field, const Glib::ustring& table
//TODO: entry->signal_user_requested_layout().connect( sigc::mem_fun(*this, &DataWidget::on_child_user_requested_layout );
child = image;
pFieldWidget = image;
m_label.set_label(title);
m_label.set_alignment(0);
......@@ -78,7 +80,6 @@ DataWidget::DataWidget(const LayoutItem_Field& field, const Glib::ustring& table
m_label.set_alignment(0);
m_label.show();
LayoutWidgetField* pFieldWidget = 0;
//Use a Combo if there is a drop-down of choices (A "value list"), else an Entry:
if(field.get_formatting_used().get_has_choices())
{
......@@ -157,7 +158,10 @@ DataWidget::DataWidget(const LayoutItem_Field& field, const Glib::ustring& table
pFieldWidget->set_layout_item(get_layout_item()->clone(), table_name); //TODO_Performance: We only need this for the numerical format.
}
}
if(pFieldWidget)
{
pFieldWidget->signal_edited().connect( sigc::mem_fun(*this, &DataWidget::on_widget_edited) );
pFieldWidget->signal_user_requested_layout().connect( sigc::mem_fun(*this, &DataWidget::on_child_user_requested_layout) );
......@@ -167,7 +171,12 @@ DataWidget::DataWidget(const LayoutItem_Field& field, const Glib::ustring& table
child = dynamic_cast<Gtk::Widget*>(pFieldWidget);
int width = get_suitable_width(field);
child->set_size_request(width, -1 /* auto */);
if(glom_type == Field::TYPE_IMAGE) //GtkImage widgets default to no size (invisible) if they are empty.
child->set_size_request(width, 200);
else
child->set_size_request(width, -1 /* auto */);
child->show_all();
}
......
......@@ -21,6 +21,7 @@
#include "imageglom.h"
#include <glibmm/i18n.h>
#include "../application.h"
#include "../data_structure/glomconversions.h"
//#include <sstream> //For stringstream
#include <iostream> // for cout, endl
......@@ -112,6 +113,11 @@ App_Glom* ImageGlom::get_application()
return dynamic_cast<App_Glom*>(pWindow);
}
bool ImageGlom::get_has_original_data() const
{
return true; //TODO.
}
void ImageGlom::set_value(const Gnome::Gda::Value& value)
{
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
......@@ -122,19 +128,69 @@ void ImageGlom::set_value(const Gnome::Gda::Value& value)
const gpointer pData = value.get_binary(&size);
if(size && pData)
{
Glib::RefPtr<Gdk::PixbufLoader> refPixbufLoader = Gdk::PixbufLoader::create("png");
try
//libgda does not currently properly unescape binary data,
//so pData is actually a null terminated string, of escaped binary data.
//This workaround should be removed when libgda is fixed:
size_t buffer_binary_length = 0;
guchar* buffer_binary = Glom_PQunescapeBytea((const guchar*)pData /* must be null-terminated */, &buffer_binary_length); //freed by us later.
if(buffer_binary)
{
guint8* puiData = (guint8*)pData;
refPixbufLoader->write(puiData, (glong)size);
m_image.set( refPixbufLoader->get_pixbuf() );
//typedef std::list<Gdk::PixbufFormat> type_list_formats;
//const type_list_formats formats = Gdk::Pixbuf::get_formats();
//std::cout << "Debug: Supported pixbuf formats:" << std::endl;
//for(type_list_formats::const_iterator iter = formats.begin(); iter != formats.end(); ++iter)
//{
// std::cout << " name=" << iter->get_name() << ", writable=" << iter->is_writable() << std::endl;
//}
scale();
}
catch(const Glib::Exception& ex)
{
g_warning("ImageGlom::set_value(): PixbufLoader::write() failed.");
Glib::RefPtr<Gdk::PixbufLoader> refPixbufLoader;
// PixbufLoader::create() is broken in gtkmm before 2.6.something,
// so let's do this in C so it works with all 2.6 versions:
GError* error = 0;
GdkPixbufLoader* loader = gdk_pixbuf_loader_new_with_type("png", &error);
if(!error)
refPixbufLoader = Glib::wrap(loader);
/*
try
{
refPixbufLoader = Gdk::PixbufLoader::create("png");
g_warning("debug a1");
}
catch(const Gdk::PixbufError& ex)
{
refPixbufLoader.clear();
g_warning("PixbufLoader::create failed: %s",ex.what().c_str());
}
*/
if(refPixbufLoader)
{
try
{
guint8* puiData = (guint8*)buffer_binary;
//g_warning("ImageGlom::set_value(): debug: from db: ");
//for(int i = 0; i < 10; ++i)
// g_warning("%02X (%c), ", (guint8)puiData[i], (char)puiData[i]);
refPixbufLoader->write(puiData, (glong)buffer_binary_length);
m_pixbuf_original = refPixbufLoader->get_pixbuf();
m_image.set(m_pixbuf_original);
scale();
}
catch(const Glib::Exception& ex)
{
g_warning("ImageGlom::set_value(): PixbufLoader::write() failed: %s", ex.what().c_str());
}
refPixbufLoader->close();
free(buffer_binary);
}
}
//TODO: load the image, using the mime type stored elsewhere.
......@@ -152,15 +208,19 @@ Gnome::Gda::Value ImageGlom::get_value() const
//Don't store the original here any longer than necessary,
Gnome::Gda::Value result; //TODO: Initialize it as binary.
Glib::RefPtr<const Gdk::Pixbuf> pixbuf = m_image.get_pixbuf();
if(pixbuf)
if(m_pixbuf_original)
{
try
{
gchar* buffer = 0;
gsize buffer_size = 0;
std::list<Glib::ustring> list_empty;
//pixbuf->save_to_buffer(buffer, buffer_size, "png", list_empty, list_empty);
m_pixbuf_original->save_to_buffer(buffer, buffer_size, "png", list_empty, list_empty); //Always store images as PNG in the database.
//g_warning("ImageGlom::get_value(): debug: to db: ");
//for(int i = 0; i < 10; ++i)
// g_warning("%02X (%c), ", (guint8)buffer[i], buffer[i]);
result.set(buffer, buffer_size);
g_free(buffer);
......@@ -177,17 +237,20 @@ Gnome::Gda::Value ImageGlom::get_value() const
void ImageGlom::scale()
{
Glib::RefPtr<Gdk::Pixbuf> pixbuf = m_image.get_pixbuf();
Glib::RefPtr<Gdk::Pixbuf> pixbuf = m_pixbuf_original;
const Gtk::Allocation allocation = m_image.get_allocation();
const int pixbuf_height = pixbuf->get_height();
const int pixbuf_width = pixbuf->get_width();
if( (pixbuf_height > allocation.get_height()) ||
(pixbuf_width > allocation.get_width()) )
if(pixbuf)
{
pixbuf = scale_keeping_ratio(pixbuf, allocation.get_height(), allocation.get_width());
m_image.set(pixbuf);
const Gtk::Allocation allocation = m_image.get_allocation();
const int pixbuf_height = pixbuf->get_height();
const int pixbuf_width = pixbuf->get_width();
if( (pixbuf_height > allocation.get_height()) ||
(pixbuf_width > allocation.get_width()) )
{
pixbuf = scale_keeping_ratio(pixbuf, allocation.get_height(), allocation.get_width());
m_image.set(pixbuf);
}
}
}
......
......@@ -38,6 +38,7 @@ public:
virtual void set_value(const Gnome::Gda::Value& value);
virtual Gnome::Gda::Value get_value() const;
virtual bool get_has_original_data() const;
protected:
......
......@@ -21,6 +21,7 @@
#include "layoutwidgetfield.h"
LayoutWidgetField::LayoutWidgetField()
: m_entered_data_stored(false)
{
}
......@@ -32,3 +33,10 @@ LayoutWidgetField::type_signal_edited LayoutWidgetField::signal_edited()
{
return m_signal_edited;
}
bool LayoutWidgetField::get_has_original_data() const
{
//Most widgets always have the original data.
//Override this method for widgets that don't.
return true;
}
......@@ -32,12 +32,18 @@ public:
virtual void set_value(const Gnome::Gda::Value& value) = 0;
virtual Gnome::Gda::Value get_value() const = 0;
/**Whether this widget still has the original entered data, instead of just a representation.
* For intance, and image widget might only store a preview.
*/
virtual bool get_has_original_data() const;
typedef sigc::signal<void> type_signal_edited;
type_signal_edited signal_edited();
protected:
type_signal_edited m_signal_edited;
bool m_entered_data_stored;
};
#endif //GLOM_MODE_DATA_LAYOUT_WIDGET_FIELD_H
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment