imageglom.cc 16.3 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
/* 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>
23
#include <glom/application.h>
24
#include <glom/utils_ui.h>
25
#include <glom/glade_utils.h>
26
#include <libglom/data_structure/glomconversions.h>
27
28
29
30
//#include <sstream> //For stringstream

#include <iostream>   // for cout, endl

31
32
33
namespace Glom
{

34
ImageGlom::ImageGlom()
35
36
: 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)
37
{
38
39
40
  init();
}

41
ImageGlom::ImageGlom(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* builder */)
42
43
44
: Gtk::EventBox(cobject),
  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)
45
46
47
48
49
50
51
52
{
  init();
}

void ImageGlom::init()
{
  m_read_only = false;

53
#ifndef GLOM_ENABLE_CLIENT_ONLY
54
  setup_menu();
55
56
57
58
59
60
#endif // !GLOM_ENABLE_CLIENT_ONLY

#ifndef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
  signal_button_press_event().connect(sigc::mem_fun(*this, &ImageGlom::on_button_press_event), false);
  signal_expose_event().connect(sigc::mem_fun(*this, &ImageGlom::on_expose_event));
#endif
61

62
  setup_menu_usermode();
63

64
  m_image.set_size_request(150, 150);
65
  m_image.show();
66

67
68
69
  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();
70

71
  add(m_frame);
72
73
}

74
75


76
77
78
79
ImageGlom::~ImageGlom()
{
}

80
81
82
void ImageGlom::set_layout_item(const sharedptr<LayoutItem>& layout_item, const Glib::ustring& table_name)
{
  LayoutWidgetField::set_layout_item(layout_item, table_name);
83
#ifdef GTKMM_ATKMM_ENABLED
84
  get_accessible()->set_name(layout_item->get_name());
85
#endif  
86
}
87
88
89
90

bool ImageGlom::on_button_press_event(GdkEventButton *event)
{
  GdkModifierType mods;
91
  gdk_window_get_pointer( gtk_widget_get_window (Gtk::Widget::gobj()), 0, 0, &mods );
92

93
94
  //Enable/Disable items.
  //We did this earlier, but get_application is more likely to work now:
95
  Application* pApp = get_application();
96
97
  if(pApp)
  {
98
#ifndef GLOM_ENABLE_CLIENT_ONLY
99
100
101
102
103
104
    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.
105
#endif // !GLOM_ENABLE_CLIENT_ONLY
106
107
108

    //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.
109
#ifndef GLOM_ENABLE_CLIENT_ONLY
110
111
112
113
114
115
116
117
118
    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.
      }
    }
119
    else
120
#endif // !GLOM_ENABLE_CLIENT_ONLY
121
    {
122
      // We cannot be in developer mode in client only mode.
123
124
125
126
127
128
129
      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.
      }
    }
130

131
132
    //Single-click to select file:
    if(mods & GDK_BUTTON1_MASK)
133
    {
134
135
136
      on_menupopup_activate_select_file();
      return true; //We handled this event.

137
138
139
    }
  }

140
#ifdef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
141
  return Gtk::EventBox::on_button_press_event(event);
142
143
#else
  return false;
144
#endif // GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
145
146
}

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

152
  return dynamic_cast<Application*>(pWindow);
153
154
}

155
156
157
158
bool ImageGlom::get_has_original_data() const
{
  return true; //TODO.
}
159

160
161
162
163
164
165
166
167
void ImageGlom::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
  m_pixbuf_original = pixbuf;
  m_image.set(m_pixbuf_original);

  scale();
}

168
void ImageGlom::set_value(const Gnome::Gda::Value& value)
169
{
Armin Burgmeier's avatar
Armin Burgmeier committed
170
171
172
  // Remember original data 
  m_original_data = Gnome::Gda::Value();
  m_original_data = value;
173
  Glib::RefPtr<Gdk::Pixbuf> pixbuf = Utils::get_pixbuf_for_gda_value(value);
Armin Burgmeier's avatar
Armin Burgmeier committed
174

175
  if(pixbuf)
176
  {
177
178
    set_pixbuf(pixbuf);
    scale();
179
  }
180
  else
181
  {
182
183
184
185
186
187
188
189
    /*
    std::cout << "Debug: Setting MISSING_IMAGE" << std::endl;
    
    //Check that this stock icon size is really available,
    //though it would be a distro error if it is not.
    Glib::RefPtr<Gtk::Style> style = get_style();
    if(style)
    {
190
      /std::cout << "Debug: Setting MISSING_IMAGE 3" << std::endl;
191
192
193
194
195
196
197
198
199
200
201
202

      const Gtk::IconSet iconset = style->lookup_icon_set(Gtk::Stock::MISSING_IMAGE);

      std::cout << "Debug: Setting MISSING_IMAGE 4" << std::endl;

      typedef std::vector<Gtk::IconSize> type_vecSizes;
      type_vecSizes sizes = iconset.get_sizes();
      type_vecSizes::iterator iterFind = std::find(sizes.begin(), sizes.end(), Gtk::ICON_SIZE_DIALOG);
      if(iterFind != sizes.end())
      {
     */
        m_image.set(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG);
203
     /*
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
      }
      else
      {
        std::cerr << "Glom: The current theme does not seem to havae the Gtk::Stock::MISSING_IMAGE icon in size Gtk::ICON_SIZE_DIALOG" << std::endl;

        if(!sizes.empty() && (sizes[0] > 0))
        {
          std::cerr << "  Using alternative stock icon size." << std::endl;
          m_image.set(Gtk::Stock::MISSING_IMAGE, sizes[0]);
        }
        else
        {
          std::cerr << "  No alternative stock icon size available either, for this stock icon." << std::endl;
          m_image.set("");
        }
      }
    }
    else
    {
      std::cerr << "Glom: No Gtk::Style available for this widget (yet), so not setting MISSING_IMAGE icon." << std::endl;
      m_image.set("");
    }
    */

    m_pixbuf_original = Glib::RefPtr<Gdk::Pixbuf>();
229
  }
230
231
232
233
}

Gnome::Gda::Value ImageGlom::get_value() const
{
234
235
  //TODO: Return the data from the file that was just chosen.
  //Don't store the original here any longer than necessary,
Armin Burgmeier's avatar
Armin Burgmeier committed
236
237
  if(m_original_data.get_value_type() != G_TYPE_NONE)
    return m_original_data;
238
  
239
  if(m_pixbuf_original)
240
  {
241
#ifdef GLIBMM_EXCEPTIONS_ENABLED
242
    try
243
#endif
244
245
246
    {
      gchar* buffer = 0;
      gsize buffer_size = 0;
247
248
249
250
      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");
251

252
#ifdef GLIBMM_EXCEPTIONS_ENABLED
253
      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.
254
255
256
257
#else
      std::auto_ptr<Glib::Error> error;
      m_pixbuf_original->save_to_buffer(buffer, buffer_size, GLOM_IMAGE_FORMAT, list_keys, list_values, error); //Always store images as the standard format in the database.
#endif
258

259
260
261
      //g_warning("ImageGlom::get_value(): debug: to db: ");
      //for(int i = 0; i < 10; ++i)
      //  g_warning("%02X (%c), ", (guint8)buffer[i], buffer[i]);
262

Armin Burgmeier's avatar
Armin Burgmeier committed
263
264
265
266
267
268
269
      GdaBinary* bin = g_new(GdaBinary, 1);
      bin->data = reinterpret_cast<guchar*>(buffer);
      bin->binary_length = buffer_size;

      m_original_data = Gnome::Gda::Value();
      m_original_data.Glib::ValueBase::init(GDA_TYPE_BINARY);
      gda_value_take_binary(m_original_data.gobj(), bin);
270

271
      buffer = 0;
Armin Burgmeier's avatar
Armin Burgmeier committed
272
      return m_original_data;
273
    }
274
#ifdef GLIBMM_EXCEPTIONS_ENABLED
275
    catch(const Glib::Exception& ex)
276
    {
277
      std::cerr << "ImageGlom::get_value(): " << ex.what() << std::endl;
278
    }
279
#endif // GLIBMM_EXCEPTIONS_ENABLED
280
  }
281

Armin Burgmeier's avatar
Armin Burgmeier committed
282
  return Gnome::Gda::Value();
283
}
284

285
#ifdef GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
286
287
288
bool ImageGlom::on_expose_event(GdkEventExpose* event)
{
  const bool result = Gtk::EventBox::on_expose_event(event);
289
#else
290
291
bool ImageGlom::on_expose_event(GdkEventExpose* /* event */)
{
292
293
  const bool result = false;
#endif // GLIBMM_DEFAULT_SIGNAL_HANDLERS_ENABLED
294
295
296
297
  scale();
  return result;
}

298
299
void ImageGlom::scale()
{
300
  Glib::RefPtr<Gdk::Pixbuf> pixbuf = m_pixbuf_original;
301

302
  if(pixbuf)
303
  {
304
305
306
    const Gtk::Allocation allocation = m_image.get_allocation();
    const int pixbuf_height = pixbuf->get_height();
    const int pixbuf_width = pixbuf->get_width();
307

308
309
310
    if( (pixbuf_height > allocation.get_height()) ||
        (pixbuf_width > allocation.get_width()) )
    {
311
312
      if(allocation.get_height() > 10 || allocation.get_width() > 10)
      {
313
        Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled = Utils::image_scale_keeping_ratio(pixbuf, allocation.get_height(), allocation.get_width());
314
315
        if(!pixbuf_scaled)
        {
316
          std::cerr << "Utils::image_scale_keeping_ratio() returned NULL pixbuf." << std::endl;
317
318
319
320
        }
        else 
        {
          //Don't set a new pixbuf if the dimenstions have not changed:
321
322
323
324
325
          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();

326
327
328
329
          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);
        }
      }
330
    }
331
  }
332
333
  //else
  //  g_warning("ImageGlom::scale(): attempt to scale a null pixbuf.");
334
335
}

336
337
void ImageGlom::on_menupopup_activate_select_file()
{
338
339
340
  if(m_read_only)
    return;

341
  //TODO: Use Hildon::FileChooser for Maemo.
Murray Cumming's avatar
Murray Cumming committed
342
  Gtk::FileChooserDialog dialog(_("Choose Image"), Gtk::FILE_CHOOSER_ACTION_OPEN);
343

344
345
  //Get image formats only:
  Gtk::FileFilter filter;
346
  filter.set_name(_("Images"));
347
348
  filter.add_pixbuf_formats();
  dialog.add_filter(filter);
349

350
351
352
353
  dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  dialog.add_button(_("Select"), Gtk::RESPONSE_OK);
  int response = dialog.run();
  dialog.hide();
354

355
  if((response != Gtk::RESPONSE_CANCEL) && (response != Gtk::RESPONSE_DELETE_EVENT))
356
  {
357
358
    const Glib::ustring uri = dialog.get_uri();
    if(!uri.empty())
359
    {
360
      Dialog_Image_Progress* dialog;
361
      Utils::get_glade_widget_derived_with_warning(dialog);
362
      if(dialog)
363
      {
364
365
        // Automatically delete the dialog when we no longer need it:
        std::auto_ptr<Gtk::Dialog> dialog_keeper(dialog);
Armin Burgmeier's avatar
Armin Burgmeier committed
366

367
        Application* pApp = get_application();
368
369
370
        if(pApp)
          dialog->set_transient_for(*pApp);

371
        dialog->load(uri);
Armin Burgmeier's avatar
Armin Burgmeier committed
372

373
        if(dialog->run() == Gtk::RESPONSE_ACCEPT)
Armin Burgmeier's avatar
Armin Burgmeier committed
374
        {
375
376
377
378
379
380
381
382
383
384
          GdaBinary* bin = g_new(GdaBinary, 1);
          std::auto_ptr<GdaBinary> image_data = dialog->get_image_data();
          bin->data = image_data->data;
          bin->binary_length = image_data->binary_length;

          m_original_data = Gnome::Gda::Value();
          m_original_data.Glib::ValueBase::init(GDA_TYPE_BINARY);
          gda_value_take_binary(m_original_data.gobj(), bin);

          m_pixbuf_original = dialog->get_pixbuf();
Armin Burgmeier's avatar
Armin Burgmeier committed
385
386
387
388
          m_image.set(m_pixbuf_original); //Load the image.
          scale();
          signal_edited().emit();
        }
389
390
      }
    }
391
  }
392
393
}

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
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.");
409
  }
410
411
412
413
}

void ImageGlom::on_clipboard_clear()
{
414
415
416
  if(m_read_only)
    return;

417
  m_pixbuf_clipboard.reset();
418
419
}

420
421
void ImageGlom::on_menupopup_activate_copy()
{
422
423
424
425
426
427
  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
428
    m_pixbuf_clipboard.reset();
429

430
431
432
433
434
435
  Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();

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

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

437
438
  refClipboard->set( listTargets, sigc::mem_fun(*this, &ImageGlom::on_clipboard_get), sigc::mem_fun(*this, &ImageGlom::on_clipboard_clear) );
}
439

440
441
void ImageGlom::on_clipboard_received_image(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
442
443
444
  if(m_read_only)
    return;

445
446
  if(pixbuf)
  {
Armin Burgmeier's avatar
Armin Burgmeier committed
447
448
449
    // Clear original data of previous image
    m_original_data = Gnome::Gda::Value();

450
    m_pixbuf_original = pixbuf;
451

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

458

459
460
void ImageGlom::on_menupopup_activate_paste()
{
461
462
463
  if(m_read_only)
    return;

464
465
  //Tell the clipboard to call our method when it is ready:
  Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();
466

467
468
  if(refClipboard)
    refClipboard->request_image( sigc::mem_fun(*this, &ImageGlom::on_clipboard_received_image) );
469
470
}

Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
471
472
void ImageGlom::on_menupopup_activate_clear()
{
473
474
475
  if(m_read_only)
    return;

476
  m_pixbuf_original.reset();
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
477
  m_image.set(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG);
478
  signal_edited().emit();
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
479
480
}

481
482
483
void ImageGlom::setup_menu_usermode()
{
  m_refActionGroup_UserModePopup = Gtk::ActionGroup::create();
484

485
486
487
488
  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
489
  m_refActionClear = Gtk::Action::create("ContextClear", Gtk::Stock::CLEAR);
490

491
492
493
494
495
496
497
498
  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) );
499

Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
500
501
  m_refActionGroup_UserModePopup->add(m_refActionClear,
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_clear) );
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());

509
#ifdef GLIBMM_EXCEPTIONS_ENABLED
510
  try
511
512
513
#else
  std::auto_ptr<Glib::Error> error;
#endif // GLIBMM_EXCEPTIONS_ENABLED
514
515
516
517
518
519
520
  {
    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
521
        "    <menuitem action='ContextClear'/>"
522
523
524
        "  </popup>"
        "</ui>";

525
#ifdef GLIBMM_EXCEPTIONS_ENABLED
526
    m_refUIManager_UserModePopup->add_ui_from_string(ui_info);
527
528
529
#else
    m_refUIManager_UserModePopup->add_ui_from_string(ui_info, error);
#endif // GLIBMM_EXCEPTIONS_ENABLED
530
  }
531
#ifdef GLIBMM_EXCEPTIONS_ENABLED
532
533
  catch(const Glib::Error& ex)
  {
534
#else
535
  if(error.get())
536
537
538
  {
    const Glib::Error& ex = *error.get();
#endif
539
540
541
542
543
544
545
546
    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");
}
547
548
549
550
551
552
553
554
555
556
557

void ImageGlom::do_choose_image()
{
  on_menupopup_activate_select_file();
}

void ImageGlom::set_read_only(bool read_only)
{
  m_read_only = read_only;
}

558
559

} //namespace Glom