imageglom.cc 16.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Glom
 *
 * Copyright (C) 2001-2004 Murray Cumming
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "imageglom.h"
#include <glibmm/i18n.h>
#include "../application.h"
24
#include "../data_structure/glomconversions.h"
25
26
27
28
//#include <sstream> //For stringstream

#include <iostream>   // for cout, endl

29
30
31
//JPEG seems to give ugly results when saved to the database and shown again.
//#define GLOM_IMAGE_FORMAT "jpeg"
//#define GLOM_IMAGE_FORMAT_MIME_TYPE "image/jpeg"
32
33
34
#define GLOM_IMAGE_FORMAT "png"
#define GLOM_IMAGE_FORMAT_MIME_TYPE "image/png"

35
ImageGlom::ImageGlom()
36
37
: m_image(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG), //The widget is invisible if we don't specify an image.
  m_pMenuPopup_UserMode(0)
38
39
{
  setup_menu();
40
  setup_menu_usermode();
41

42
  m_image.set_size_request(150, 150);
43
  m_image.show();
44

45
46
47
  m_frame.set_shadow_type(Gtk::SHADOW_ETCHED_IN); //Without this, the image widget has no borders and is completely invisible when empty.
  m_frame.add(m_image);
  m_frame.show();
48

49
  add(m_frame);
50
51
52
53
54
55
56
57
58
59
60
}

ImageGlom::~ImageGlom()
{
}


bool ImageGlom::on_button_press_event(GdkEventButton *event)
{
  GdkModifierType mods;
  gdk_window_get_pointer( Gtk::Widget::gobj()->window, 0, 0, &mods );
61

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
  //Enable/Disable items.
  //We did this earlier, but get_application is more likely to work now:
  App_Glom* pApp = get_application();
  if(pApp)
  {
    pApp->add_developer_action(m_refContextLayout); //So that it can be disabled when not in developer mode.
    pApp->add_developer_action(m_refContextAddField);
    pApp->add_developer_action(m_refContextAddRelatedRecords);
    pApp->add_developer_action(m_refContextAddGroup);

    pApp->update_userlevel_ui(); //Update our action's sensitivity.

    //Only show this popup in developer mode, so operators still see the default GtkEntry context menu.
    //TODO: It would be better to add it somehow to the standard context menu.
    if(pApp->get_userlevel() == AppState::USERLEVEL_DEVELOPER)
    {
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
        m_pMenuPopup->popup(event->button, event->time);
        return true; //We handled this event.
      }
    }
85
86
87
88
89
90
91
92
93
    else
    {
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
        m_pMenuPopup_UserMode->popup(event->button, event->time);
        return true; //We handled this event.
      }
    }
94

95
96
    //Single-click to select file:
    if(mods & GDK_BUTTON1_MASK)
97
    {
98
99
100
      on_menupopup_activate_select_file();
      return true; //We handled this event.

101
102
103
104
105
106
107
108
109
110
111
112
113
114
    }
  }

  return Gtk::EventBox::on_button_press_event(event);
}

App_Glom* ImageGlom::get_application()
{
  Gtk::Container* pWindow = get_toplevel();
  //TODO: This only works when the child widget is already in its parent.

  return dynamic_cast<App_Glom*>(pWindow);
}

115
116
117
118
bool ImageGlom::get_has_original_data() const
{
  return true; //TODO.
}
119

120
void ImageGlom::set_value(const Gnome::Gda::Value& value)
121
{
122
  bool pixbuf_set = false;
123

124
125
126
  if(value.get_value_type() == Gnome::Gda::VALUE_TYPE_BINARY)
  {
    glong size = 0;
Murray Cumming's avatar
Murray Cumming committed
127
    const gpointer pData = value.get_binary(size);
128
129
    if(size && pData)
    {
130
131
132
      //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:
133
      //(It is fixed in libgd-2.0 but is unlikely to be fixed in libgda-1.2)
134
135
136
      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)
137
      {
138
139
140
141
142
143
144
        //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;
        //}
145

146
        Glib::RefPtr<Gdk::PixbufLoader> refPixbufLoader;
147

148
149
150
        // 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;
151
        GdkPixbufLoader* loader = gdk_pixbuf_loader_new_with_type(GLOM_IMAGE_FORMAT, &error);
152
153
        if(!error)
          refPixbufLoader = Glib::wrap(loader);
154
155
        else
          std::cerr << "ImageGlom::set_value(): Error while calling gdk_pixbuf_loader_new_with_type()." << std::endl;
156

157
158
159
        /*
        try
        {
160
          refPixbufLoader = Gdk::PixbufLoader::create(GLOM_IMAGE_FORMAT);
161
162
163
164
165
166
167
168
          g_warning("debug a1");
        }
        catch(const Gdk::PixbufError& ex)
        {
          refPixbufLoader.clear();
          g_warning("PixbufLoader::create failed: %s",ex.what().c_str());
        }
        */
169

170
171
172
173
174
        if(refPixbufLoader)
        {
          try
          {
            guint8* puiData = (guint8*)buffer_binary;
175

176
177
178
            //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]);
179

180
            refPixbufLoader->write(puiData, (glong)buffer_binary_length);
181

182
183
            m_pixbuf_original = refPixbufLoader->get_pixbuf();
            m_image.set(m_pixbuf_original);
184
            pixbuf_set = true;
185

186
            scale();
187

188
            refPixbufLoader->close(); //This throws if write() threw, so it must be inside the try block.
189
190
191
192
193
          }
          catch(const Glib::Exception& ex)
          {
            g_warning("ImageGlom::set_value(): PixbufLoader::write() failed: %s", ex.what().c_str());
          }
194

195
196
          free(buffer_binary);
        }
197
      }
198

199
200
201
      //TODO: load the image, using the mime type stored elsewhere.
      //pixbuf = Gdk::Pixbuf::create_from_data(
    }
202

203
  }
204

205
206
207
208
  if(!pixbuf_set)
  {
    m_image.set(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG);
  }
209
210
211
212
}

Gnome::Gda::Value ImageGlom::get_value() const
{
213
214
215
  //TODO: Return the data from the file that was just chosen.
  //Don't store the original here any longer than necessary,
  Gnome::Gda::Value result; //TODO: Initialize it as binary.
216

217
  if(m_pixbuf_original)
218
219
220
221
222
  {
    try
    {
      gchar* buffer = 0;
      gsize buffer_size = 0;
223
224
225
226
      std::list<Glib::ustring> list_keys;
      std::list<Glib::ustring> list_values;
      //list_keys.push_back("quality"); //For jpeg only.
      //list_values.push_back("95");
227

228
      m_pixbuf_original->save_to_buffer(buffer, buffer_size, GLOM_IMAGE_FORMAT, list_keys, list_values); //Always store images as the standard format in the database.
229

230
231
232
      //g_warning("ImageGlom::get_value(): debug: to db: ");
      //for(int i = 0; i < 10; ++i)
      //  g_warning("%02X (%c), ", (guint8)buffer[i], buffer[i]);
233
234
235
236
237
238

      //libgda currently assumes that this buffer is an already-escaped string.
      //TODO: Just use the raw buffer when libgda (in libgda-2.0) has been fixed:
      Glib::ustring binary_escaped = GlomConversions::get_escaped_binary_data((guint8*)buffer, buffer_size);
      result.set(binary_escaped.c_str(), binary_escaped.size());

239
240
241
242
243
      g_free(buffer);
      buffer = 0;
    }
    catch(const Glib::Exception& /* ex */)
    {
244

245
246
    }
  }
247

248
249
  return result;
}
250

251
252
253
254
255
256
257
bool ImageGlom::on_expose_event(GdkEventExpose* event)
{
  const bool result = Gtk::EventBox::on_expose_event(event);
  scale();
  return result;
}

258
259
void ImageGlom::scale()
{
260
  Glib::RefPtr<Gdk::Pixbuf> pixbuf = m_pixbuf_original;
261

262
  if(pixbuf)
263
  {
264
265
266
    const Gtk::Allocation allocation = m_image.get_allocation();
    const int pixbuf_height = pixbuf->get_height();
    const int pixbuf_width = pixbuf->get_width();
267

268
269
270
    if( (pixbuf_height > allocation.get_height()) ||
        (pixbuf_width > allocation.get_width()) )
    {
271
272
273
274
275
276
277
278
279
280
      if(allocation.get_height() > 10 || allocation.get_width() > 10)
      {
        Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled = scale_keeping_ratio(pixbuf, allocation.get_height(), allocation.get_width());
        if(!pixbuf_scaled)
        {
          std::cerr << "ImageGlom::scale(): scale_keeping_ratio() returned NULL pixbuf." << std::endl;
        }
        else 
        {
          //Don't set a new pixbuf if the dimenstions have not changed:
281
282
283
284
285
          Glib::RefPtr<const Gdk::Pixbuf> pixbuf_in_image;

          if(m_image.get_storage_type() == Gtk::IMAGE_PIXBUF) //Prevent warning.
            pixbuf_in_image = m_image.get_pixbuf();

286
287
288
289
          if( !pixbuf_in_image || (pixbuf_in_image->get_height() != pixbuf_scaled->get_height()) || (pixbuf_in_image->get_width() != pixbuf_scaled->get_width()) )
            m_image.set(pixbuf_scaled);
        }
      }
290
    }
291
  }
292
293
  //else
  //  g_warning("ImageGlom::scale(): attempt to scale a null pixbuf.");
294
295
}

296
//static:
297
Glib::RefPtr<Gdk::Pixbuf> ImageGlom::scale_keeping_ratio(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf, int target_height, int target_width)
298
{
299
300
  if( (target_height == 0) || (target_width == 0) )
    return Glib::RefPtr<Gdk::Pixbuf>(); //This shouldn't happen anyway.
301

302
303
304
305
306
307
  enum enum_scale_mode
  {
    SCALE_WIDTH,
    SCALE_HEIGHT,
    SCALE_NONE
  };
308

309
310
311
312
  enum_scale_mode scale_mode = SCALE_NONE; //Start with either the width or height, and scale the other according to the ratio.

  const int pixbuf_height = pixbuf->get_height();
  const int pixbuf_width = pixbuf->get_width();
313

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
  if(pixbuf_height > target_height)
  {
    if(pixbuf_width > target_width)
    {
      //Both are bigger than the target, so find the biggest one:
      if(pixbuf_width > pixbuf_height)
        scale_mode = SCALE_WIDTH;
      else
        scale_mode = SCALE_HEIGHT;
    }
    else
    {
      //Only the height is bigger:
      scale_mode = SCALE_HEIGHT;
    }
  }
  else if(pixbuf_width > target_width)
  {
    //Only the height is bigger:
    scale_mode = SCALE_WIDTH;
  }
335

336
337
338
339
  if(scale_mode == SCALE_NONE)
    return pixbuf;
  else if(scale_mode == SCALE_HEIGHT)
  {
340
    const float ratio = (float)target_height / (float)pixbuf_height; 
341
342
343
344
    target_width = (int)((float)pixbuf_width * ratio);
  }
  else if(scale_mode == SCALE_WIDTH)
  {
345
    const float ratio = (float)target_width / (float) pixbuf_width;
346
347
    target_height = (int)((float)pixbuf_height * ratio);
  }
348

Murray Cumming's avatar
Murray Cumming committed
349
350
 if( (target_height == 0) || (target_width == 0) )
 {
351
   return Glib::RefPtr<Gdk::Pixbuf>(); //This shouldn't happen anyway. It seems to happen sometimes though, when ratio is very small.
Murray Cumming's avatar
Murray Cumming committed
352
 }
353

354
355
  return pixbuf->scale_simple(target_width, target_height, Gdk::INTERP_NEAREST);
}
356
357
358
359

void ImageGlom::on_menupopup_activate_select_file()
{
  Gtk::FileChooserDialog dialog(_("Choose image"), Gtk::FILE_CHOOSER_ACTION_OPEN);
360

361
362
  //Get image formats only:
  Gtk::FileFilter filter;
363
  filter.set_name(_("Images"));
364
365
  filter.add_pixbuf_formats();
  dialog.add_filter(filter);
366

367
368
369
370
  dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  dialog.add_button(_("Select"), Gtk::RESPONSE_OK);
  int response = dialog.run();
  dialog.hide();
371

372
373
374
375
376
377
378
379
380
381
382
383
384
385
  if(response != Gtk::RESPONSE_CANCEL)
  {
    const std::string filepath = dialog.get_filename();
    if(!filepath.empty())
    {
      try
      {
        m_pixbuf_original = Gdk::Pixbuf::create_from_file(filepath);
        if(m_pixbuf_original)
        {
          m_image.set(m_pixbuf_original); //Load the image.
          scale();
          signal_edited().emit();
        }
386
387
388
389
        else
        {
          g_warning("ImageGlom::on_menupopup_activate_select_file(): file load failed.");
        }
390
391
392
393
394
      }
      catch(const Glib::Exception& ex)
      {
        App_Glom* pApp = get_application();
        if(pApp)
395
          Frame_Glom::show_ok_dialog(_("Image loading failed"), _("The image file could not be opened:\n") + ex.what(), *pApp, Gtk::MESSAGE_ERROR);
396
397
      }
    }
398
  }
399
400
}

401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
void ImageGlom::on_clipboard_get(Gtk::SelectionData& selection_data, guint /* info */)
{
  //info is meant to indicate the target, but it seems to be always 0,
  //so we use the selection_data's target instead.

  const std::string target = selection_data.get_target(); 

  if(target == GLOM_IMAGE_FORMAT_MIME_TYPE)
  {
    // This set() override uses an 8-bit text format for the data.
    selection_data.set_pixbuf(m_pixbuf_clipboard);
  }
  else
  {
    g_warning("ExampleWindow::on_clipboard_get(): Unexpected clipboard target format.");
  } 
}

void ImageGlom::on_clipboard_clear()
{
  m_pixbuf_clipboard.clear();
}

424
425
void ImageGlom::on_menupopup_activate_copy()
{
426
427
428
429
430
431
432
433
  if(m_pixbuf_original)
  {
    //When copy is used, store it here until it is pasted.
    m_pixbuf_clipboard = m_pixbuf_original->copy(); //TODO: Get it from the DB, when we stop storing the original here instead of just the preview.
  }
  else
    m_pixbuf_clipboard.clear();

434
435
436
437
438
439
  Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();

  //Targets:
  std::list<Gtk::TargetEntry> listTargets;

  listTargets.push_back( Gtk::TargetEntry(GLOM_IMAGE_FORMAT_MIME_TYPE) );
440

441
442
  refClipboard->set( listTargets, sigc::mem_fun(*this, &ImageGlom::on_clipboard_get), sigc::mem_fun(*this, &ImageGlom::on_clipboard_clear) );
}
443

444
445
446
447
448
void ImageGlom::on_clipboard_received_image(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
  if(pixbuf)
  {
    m_pixbuf_original = pixbuf;
449

450
451
452
453
    m_image.set(m_pixbuf_original); //Load the image.
    scale();
    signal_edited().emit();
  }
454
455
}

456

457
458
void ImageGlom::on_menupopup_activate_paste()
{
459
460
  //Tell the clipboard to call our method when it is ready:
  Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
461

462
463
  if(refClipboard)
    refClipboard->request_image( sigc::mem_fun(*this, &ImageGlom::on_clipboard_received_image) );
464
465
}

Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
466
467
468
469
void ImageGlom::on_menupopup_activate_clear()
{
  m_pixbuf_original.clear();
  m_image.set(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG);
470
  signal_edited().emit();
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
471
472
}

473
474
475
void ImageGlom::setup_menu_usermode()
{
  m_refActionGroup_UserModePopup = Gtk::ActionGroup::create();
476

477
478
479
480
  m_refActionGroup_UserModePopup->add(Gtk::Action::create("ContextMenu_UserMode", "Context Menu") );
  m_refActionSelectFile =  Gtk::Action::create("ContextSelectFile", Gtk::Stock::EDIT, _("Choose File"));
  m_refActionCopy = Gtk::Action::create("ContextCopy", Gtk::Stock::COPY);
  m_refActionPaste = Gtk::Action::create("ContextPaste", Gtk::Stock::PASTE);
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
481
  m_refActionClear = Gtk::Action::create("ContextClear", Gtk::Stock::CLEAR);
482

483
484
485
486
487
488
489
490
  m_refActionGroup_UserModePopup->add(m_refActionSelectFile,
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_select_file) );

  m_refActionGroup_UserModePopup->add(m_refActionCopy,
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_copy) );

  m_refActionGroup_UserModePopup->add(m_refActionPaste,
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_paste) );
491

Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
492
493
  m_refActionGroup_UserModePopup->add(m_refActionClear,
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_clear) );
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508

  m_refUIManager_UserModePopup = Gtk::UIManager::create();

  m_refUIManager_UserModePopup->insert_action_group(m_refActionGroup_UserModePopup);

  //TODO: add_accel_group(m_refUIManager_UserModePopup->get_accel_group());

  try
  {
    Glib::ustring ui_info = 
        "<ui>"
        "  <popup name='ContextMenu_UserMode'>"
        "    <menuitem action='ContextSelectFile'/>"
        "    <menuitem action='ContextCopy'/>"
        "    <menuitem action='ContextPaste'/>"
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
509
        "    <menuitem action='ContextClear'/>"
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
        "  </popup>"
        "</ui>";

    m_refUIManager_UserModePopup->add_ui_from_string(ui_info);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << "building menus failed: " <<  ex.what();
  }

  //Get the menu:
  m_pMenuPopup_UserMode = dynamic_cast<Gtk::Menu*>( m_refUIManager_UserModePopup->get_widget("/ContextMenu_UserMode") ); 
  if(!m_pMenuPopup_UserMode)
    g_warning("menu not found");
}