imageglom.cc 28.2 KB
Newer Older
1
2
/* Glom
 *
3
 * Copyright (C) 2001-2011 Murray Cumming
4
5
6
7
8
9
10
11
12
13
14
15
16
 *
 * 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
17
18
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
19
20
21
22
 */

#include "imageglom.h"
#include <glibmm/i18n.h>
23
#include <glom/appwindow.h>
24
#include <glom/utils_ui.h>
25
#include <glom/glade_utils.h>
26
#include <libglom/data_structure/glomconversions.h>
27
28
#include <glom/utility_widgets/dialog_image_load_progress.h>
#include <glom/utility_widgets/dialog_image_save_progress.h>
29
30
#include <gtkmm/appchooserdialog.h>
#include <gtkmm/filechooserdialog.h>
31
32
#include <giomm/file.h>
#include <giomm/contenttype.h>
33
#include <giomm/menu.h>
34
#include <libgda/gda-blob-op.h>
35
#include <glibmm/convert.h>
36

37
38
39
40
#ifdef G_OS_WIN32
#include <windows.h>
#endif

41
42
#include <iostream>   // for cout, endl

43
44
45
namespace Glom
{

46
ImageGlom::type_vec_ustrings ImageGlom::m_evince_supported_mime_types;
47
ImageGlom::type_vec_ustrings ImageGlom::m_gdkpixbuf_supported_mime_types;
48

49
ImageGlom::ImageGlom()
50
51
: m_ev_view(0),
  m_ev_document_model(0),
52
  m_pMenuPopup_UserMode(0)
53
{
54
55
56
  init();
}

57
ImageGlom::ImageGlom(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* builder */)
58
: Gtk::EventBox(cobject),
59
60
  m_ev_view(0),
  m_ev_document_model(0),
61
  m_pMenuPopup_UserMode(0)
62
63
64
65
66
67
{
  init();
}

void ImageGlom::init()
{
68
  m_ev_view = EV_VIEW(ev_view_new());
69
70
71
72
73
74
75
76
  //gtk_widget_add_events(GTK_WIDGET(m_ev_view), GDK_BUTTON_PRESS_MASK);

  //Connect the the EvView's button-press-event signal, 
  //because we don't get it otherwise.
  //For some reason this is not necessary with the GtkImage.
  Gtk::Widget* cppEvView = Glib::wrap(GTK_WIDGET(m_ev_view));
  cppEvView->signal_button_press_event().connect(
    sigc::mem_fun(*this, &ImageGlom::on_button_press_event), false);
77

78
79
  m_read_only = false;

80
#ifndef GLOM_ENABLE_CLIENT_ONLY
81
  setup_menu(this);
82
83
#endif // !GLOM_ENABLE_CLIENT_ONLY

84
  setup_menu_usermode();
85

86
  //m_image.set_size_request(150, 150);
87

88

89
90
  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.show();
91

92
  add(m_frame);
93
94
}

95
96


97
98
ImageGlom::~ImageGlom()
{
99
  delete m_pMenuPopup_UserMode;
100
101
}

102
103
104
void ImageGlom::set_layout_item(const sharedptr<LayoutItem>& layout_item, const Glib::ustring& table_name)
{
  LayoutWidgetField::set_layout_item(layout_item, table_name);
105
#ifdef GTKMM_ATKMM_ENABLED
106
  get_accessible()->set_name(layout_item->get_name());
107
#endif  
108
}
109
110
111
112

bool ImageGlom::on_button_press_event(GdkEventButton *event)
{
  GdkModifierType mods;
113
  gdk_window_get_device_position( gtk_widget_get_window (Gtk::Widget::gobj()), event->device, 0, 0, &mods );
114

115
  //Enable/Disable items.
116
117
  //We did this earlier, but get_appwindow is more likely to work now:
  AppWindow* pApp = get_appwindow();
118
119
  if(pApp)
  {
120
#ifndef GLOM_ENABLE_CLIENT_ONLY
121
122
123
124
125
126
    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.
127
#endif // !GLOM_ENABLE_CLIENT_ONLY
128
129
130

    //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.
131
#ifndef GLOM_ENABLE_CLIENT_ONLY
132
133
134
135
136
    if(pApp->get_userlevel() == AppState::USERLEVEL_DEVELOPER)
    {
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
137
138
        popup_menu(event->button, event->time);
       
139
140
141
        return true; //We handled this event.
      }
    }
142
    else
143
#endif // !GLOM_ENABLE_CLIENT_ONLY
144
    {
145
      // We cannot be in developer mode in client only mode.
146
147
148
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
149
150
        popup_menu(event->button, event->time);

151
152
153
        return true; //We handled this event.
      }
    }
154

155
156
    //Single-click to select file:
    if(mods & GDK_BUTTON1_MASK)
157
    {
158
      on_menupopup_activate_select_file();
159
160
      return true; //We handled this event.

161
162
163
164
165
166
    }
  }

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

167
AppWindow* ImageGlom::get_appwindow() const
168
{
169
  Gtk::Container* pWindow = const_cast<Gtk::Container*>(get_toplevel());
170
171
  //TODO: This only works when the child widget is already in its parent.

172
  return dynamic_cast<AppWindow*>(pWindow);
173
174
}

175
176
177
178
bool ImageGlom::get_has_original_data() const
{
  return true; //TODO.
}
179

180
/*
181
182
183
void ImageGlom::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
  m_pixbuf_original = pixbuf;
184
  show_image_data();
185
}
186
*/
187

188
void ImageGlom::set_value(const Gnome::Gda::Value& value)
189
{
Armin Burgmeier's avatar
Armin Burgmeier committed
190
191
192
  // Remember original data 
  m_original_data = Gnome::Gda::Value();
  m_original_data = value;
193
  show_image_data();
194
195
196
197
}

Gnome::Gda::Value ImageGlom::get_value() const
{
198
  return m_original_data;
199
}
200

201
void ImageGlom::on_size_allocate(Gtk::Allocation& allocation)
202
{
203
  Gtk::EventBox::on_size_allocate(allocation);
204

205
  //Resize the GtkImage if necessary:
Murray Cumming's avatar
Murray Cumming committed
206
  if(m_pixbuf_original)
207
  {
208
    const Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled = get_scaled_image();
209
210
    m_image.set(pixbuf_scaled);
  }
211
212
}

213
static void image_glom_ev_job_finished(EvJob* job, void* user_data)
214
{
215
216
217
218
219
220
221
222
223
224
  g_assert(job);
  
  ImageGlom* self = (ImageGlom*)user_data;
  g_assert(self);
  
  self->on_ev_job_finished(job);
}
  
void ImageGlom::on_ev_job_finished(EvJob* job)
{
Murray Cumming's avatar
Murray Cumming committed
225
226
227
228
229
230
231
232
233
234
235
236
237
  if (ev_job_is_failed (job)) {
    g_warning ("%s", job->error->message);
    g_object_unref (job);

    return;
  }

  ev_document_model_set_document(m_ev_document_model, job->document);
  ev_document_model_set_page(m_ev_document_model, 1);
  g_object_unref (job);
  
  //TODO: Show that we are no longer loading.
  //ev_view_set_loading(m_ev_view, FALSE);
238
}
239

240
241
242
243
244
const GdaBinary* ImageGlom::get_binary() const
{
  const GdaBinary* gda_binary = 0;
  if(m_original_data.get_value_type() == GDA_TYPE_BINARY)
    gda_binary = gda_value_get_binary(m_original_data.gobj());
245
246
247
248
249
250
  else if(m_original_data.get_value_type() == GDA_TYPE_BLOB)
  {
    const GdaBlob* gda_blob = gda_value_get_blob(m_original_data.gobj());
    if(gda_blob && gda_blob_op_read_all(const_cast<GdaBlobOp*>(gda_blob->op), const_cast<GdaBlob*>(gda_blob)))
      gda_binary = &(gda_blob->data);
  }
251
252
253
254
  
  return gda_binary;
}

255
256
Glib::ustring ImageGlom::get_mime_type() const
{
257
258
  const GdaBinary* gda_binary = get_binary();

259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
  if(!gda_binary)
    return Glib::ustring();
    
  if(!gda_binary->data)
    return Glib::ustring();

  bool uncertain = false;
  const Glib::ustring result = Gio::content_type_guess(std::string(),
    gda_binary->data, gda_binary->binary_length,
    uncertain);

  //std::cout << G_STRFUNC << ": mime_type=" << result << ", uncertain=" << uncertain << std::endl;
  return result;  
}

void ImageGlom::fill_evince_supported_mime_types()
{
  //Fill the static list if it has not already been filled:
  if(!m_evince_supported_mime_types.empty())
    return;

  //Discover what mime types libevview can support.
  //Older versions supported image types too, via GdkPixbuf,
  //but that support was then removed.  
  GList* types_list = ev_backends_manager_get_all_types_info();
  if(!types_list)
  {
    return;
  }
  
  for(GList* l = types_list; l; l = g_list_next(l))
  {
Murray Cumming's avatar
Murray Cumming committed
291
292
293
294
295
296
297
298
299
300
301
302
303
    EvTypeInfo *info = (EvTypeInfo *)l->data;
    if(!info)
      continue;

    const char* mime_type = 0;
    int i = 0;
    while((mime_type = info->mime_types[i++]))
    {
      if(mime_type)
        m_evince_supported_mime_types.push_back(mime_type);
      //std::cout << "evince supported mime_type=" << mime_type << std::endl; 
    }
  }  
304
305
}

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
void ImageGlom::fill_gdkpixbuf_supported_mime_types()
{
  //Fill the static list if it has not already been filled:
  if(!m_gdkpixbuf_supported_mime_types.empty())
    return;
    
  typedef std::vector<Gdk::PixbufFormat> type_vec_formats;
  const type_vec_formats formats = Gdk::Pixbuf::get_formats();
  
  for(type_vec_formats::const_iterator iter = formats.begin();
    iter != formats.end(); ++iter)
  {
    const Gdk::PixbufFormat& format = *iter;
    const std::vector<Glib::ustring> mime_types = format.get_mime_types();
    m_gdkpixbuf_supported_mime_types.insert(
      m_gdkpixbuf_supported_mime_types.end(),
      mime_types.begin(), mime_types.end());
  }
}

326
327
328
329
void ImageGlom::show_image_data()
{
  bool use_evince = false;
  
330
331
332
  const Glib::ustring mime_type = get_mime_type();

  //std::cout << "mime_type=" << mime_type << std::endl; 
333
  
334
335
336
337
338
339
  fill_evince_supported_mime_types();
  const type_vec_ustrings::iterator iterFind = 
    std::find(m_evince_supported_mime_types.begin(),
      m_evince_supported_mime_types.end(),
      mime_type);
  if(iterFind != m_evince_supported_mime_types.end())
340
  {
341
342
    use_evince = true;
  }  
343
  
344
345
  m_frame.remove();
    
346
347
  //Clear all possible display widgets:
  m_pixbuf_original.reset();
348
  m_image.set(Glib::RefPtr<Gdk::Pixbuf>()); //TODO: Add an unset() to gtkmm.
349
350
351
352
353
354
355
356
357
358
359
  
  if(m_ev_document_model)
  {
    g_object_unref(m_ev_document_model);
    m_ev_document_model = 0;
  }

  if(use_evince)
  {
    //Use EvView:
    m_image.hide();
360
    
361
362
363
    gtk_widget_show(GTK_WIDGET(m_ev_view));
    gtk_container_add(GTK_CONTAINER(m_frame.gobj()), GTK_WIDGET(m_ev_view));

364
365
366
    // Try loading from data in memory:
    // TODO: Uncomment this if this API is added: https://bugzilla.gnome.org/show_bug.cgi?id=654832
    /*
367
    const GdaBinary* gda_binary = get_binary();
368
369
370
371
372
373
374
375
376
377
378
    if(!gda_binary || !gda_binary->data || !gda_binary->binary_length)
    {
       std::cerr << G_STRFUNC << "Data was null or empty." << std::endl;
      return;
    }
    
    EvJob *job = ev_job_load_new_with_data(
      (char*)gda_binary->data, gda_binary->binary_length);
    */
    //TODO: Test failure asynchronously.
    
379
380
381
382
383
384
385
386
387
388
    const Glib::ustring uri = save_to_temp_file(false /* don't show progress */);
    if(uri.empty())
    {
      std::cerr << G_STRFUNC << "Could not save temp file to show in the EvView." << std::endl;
    }
  
    EvJob *job = ev_job_load_new(uri.c_str());
  
    m_ev_document_model = ev_document_model_new();
    ev_view_set_model(m_ev_view, m_ev_document_model);
389
390
391
392
    ev_document_model_set_continuous(m_ev_document_model, FALSE); //Show only one page.

    //TODO: Show that we are loading.
    //ev_view_set_loading(m_ev_view, TRUE);
Murray Cumming's avatar
Murray Cumming committed
393

394
    g_signal_connect (job, "finished",
395
396
      G_CALLBACK (image_glom_ev_job_finished), this);
    ev_job_scheduler_push_job (job, EV_JOB_PRIORITY_NONE);
397
398
399
400
  }
  else
  {
    //Use GtkImage instead:
401
    gtk_widget_hide(GTK_WIDGET(m_ev_view));  
402
403
    m_image.show();
    m_frame.add(m_image);
404
405
    
    Glib::RefPtr<const Gio::Icon> icon;
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
      
    bool use_gdkpixbuf = false;
    fill_gdkpixbuf_supported_mime_types();
    const type_vec_ustrings::iterator iterFind = 
      std::find(m_gdkpixbuf_supported_mime_types.begin(),
        m_gdkpixbuf_supported_mime_types.end(),
        mime_type);
    if(iterFind != m_gdkpixbuf_supported_mime_types.end())
    {
      use_gdkpixbuf = true;
    }
    
    if(use_gdkpixbuf)
    {
      //Try to use GdkPixbuf's loader:
      m_pixbuf_original = Utils::get_pixbuf_for_gda_value(m_original_data);
    }
    else
    {
425
426
      //Get an icon for the file type;
      icon = Gio::content_type_get_icon(mime_type);
427
    }
428
    
429
    if(m_pixbuf_original)
430
431
432
433
    {
      Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled = get_scaled_image();
      m_image.set(pixbuf_scaled);
    }
434
435
436
437
    else if(icon)
    {
      m_image.set(icon, Gtk::ICON_SIZE_DIALOG);
    }
438
    else
439
    {
440
      m_image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_DIALOG);
441
    }
442
443
444
  }
}

445
Glib::RefPtr<Gdk::Pixbuf> ImageGlom::get_scaled_image()
446
447
448
449
{
  Glib::RefPtr<Gdk::Pixbuf> pixbuf = m_pixbuf_original;

  if(!pixbuf)
450
    return pixbuf;
451
452
453
454
455
 
  const Gtk::Allocation allocation = m_image.get_allocation();
  const int pixbuf_height = pixbuf->get_height();
  const int pixbuf_width = pixbuf->get_width();
    
Murray Cumming's avatar
Murray Cumming committed
456
457
  const int allocation_height = allocation.get_height();
  const int allocation_width = allocation.get_width();
458
      
Murray Cumming's avatar
Murray Cumming committed
459
460
  //std::cout << "pixbuf_height=" << pixbuf_height << ", pixbuf_width=" << pixbuf_width << std::endl;
  //std::cout << "allocation_height=" << allocation.get_height() << ", allocation_width=" << allocation.get_width() << std::endl;
461

462
  if( (pixbuf_height > allocation_height) ||
463
464
      (pixbuf_width > allocation_width) )
  {
465
    if(true) //allocation_height > 10 || allocation_width > 10)
466
    {
467
468
469
      Glib::RefPtr<Gdk::Pixbuf> pixbuf_scaled = Utils::image_scale_keeping_ratio(pixbuf, allocation_height, allocation_width);
      
      //Don't set a new pixbuf if the dimensions have not changed:
470
      Glib::RefPtr<Gdk::Pixbuf> pixbuf_in_image;
471
472
473
474
475

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

      if( !pixbuf_in_image || !pixbuf_scaled || (pixbuf_in_image->get_height() != pixbuf_scaled->get_height()) || (pixbuf_in_image->get_width() != pixbuf_scaled->get_width()) )
476
      {
Murray Cumming's avatar
Murray Cumming committed
477
        /*
478
479
480
481
482
        std::cout << "get_scale(): returning scaled" << std::endl;
        if(pixbuf_scaled)
        {
          std::cout << "scaled height=" << pixbuf_scaled->get_height() << ", scaled width=" << pixbuf_scaled->get_width() << std::endl;
        }
Murray Cumming's avatar
Murray Cumming committed
483
        */
484
485
        
        return pixbuf_scaled;
486
      }
487
      else
488
      {
489
490
491
492
        //Return the existing one, 
        //instead of a new one with the same contents,
        //so no unnecessary changes will be triggered.
        return pixbuf_in_image;
493
      }
494
    }
495
  }
496
  
Murray Cumming's avatar
Murray Cumming committed
497
  //std::cout << "get_scaled(): returning original" << std::endl;
498
  return pixbuf;
499
500
}

501
void ImageGlom::on_menupopup_activate_open_file()
502
{
503
504
505
  open_with();
}

506
void ImageGlom::on_menupopup_activate_open_file_with()
507
{
508
  AppWindow* pApp = get_appwindow();
509
510

  //Offer the user a choice of suitable applications:
511
512
513
514
515
516
517
  const Glib::ustring mime_type = get_mime_type();
  if(mime_type.empty())
  {
    std::cerr << G_STRFUNC << ": mime_type is empty." << std::endl;
  }
  
  Gtk::AppChooserDialog dialog(mime_type);
518
519
520
521
  if(pApp)
    dialog.set_transient_for(*pApp);

  if(dialog.run() != Gtk::RESPONSE_OK)
522
    return;
523
524
525
526
527
528
529
530
531
  
  Glib::RefPtr<Gio::AppInfo> app_info = dialog.get_app_info();
  if(!app_info)
  {
    std::cerr << G_STRFUNC << ": app_info was null." << std::endl;
  }
  
  open_with(app_info);
}
532

533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
static void make_file_read_only(const Glib::ustring& uri)
{
  std::string filepath;

  try
  {
    filepath = Glib::filename_from_uri(uri);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << G_STRFUNC << "Exception: " << ex.what() << std::endl;
    return;
  }
  
  if(filepath.empty())
  {
    std::cerr << G_STRFUNC << ": filepath is empty." << std::endl;
  }
  
  const int result = chmod(filepath.c_str(), S_IRUSR);
  if(result != 0)
  {
    std::cerr << G_STRFUNC << ": chmod() failed." << std::endl;
  }
  
  //Setting the attribute via gio gives us this exception:
  //"Setting attribute access::can-write not supported"
  /*
  Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(uri);

  Glib::RefPtr<Gio::FileInfo> file_info;

  try
  {
    file_info = file->query_info(G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << G_STRFUNC << ": query_info() failed: " << ex.what() << std::endl;
    return;
  }
  
  if(!file_info)
  {
    std::cerr << ": file_info is null" << std::endl;
    return;
  }
  
  const bool can_write =
    file_info->get_attribute_boolean(G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
  if(!can_write)
    return;
    
  file_info->set_attribute_boolean(G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, false);
  
  try
  {
    file->set_attributes_from_info(file_info);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << G_STRFUNC << ": set_attributes_from_info() failed: " << ex.what() << std::endl;
  }
  */
}

599
Glib::ustring ImageGlom::save_to_temp_file(bool show_progress)
600
{
601
602
  Glib::ustring uri = Utils::get_temp_file_uri("glom_image");
  if(uri.empty())
603
  {
604
    std::cerr << ": uri is empty." << std::endl;
605
  }
606
  
607
608
609
610
611
  bool saved = false;
  if(show_progress)
    saved = save_file(uri);
  else
    saved = save_file_sync(uri);
612
  
613
614
615
616
617
  if(!saved)
  {
    uri = Glib::ustring();
    std::cerr << G_STRFUNC << ": save_file() failed." << std::endl;
  }
618
619
620
621
622
623
624
  else
  {
    //Don't let people easily edit the saved file,
    //because they would lose data when it is automatically deleted later.
    //Also they might think that editing it will change it in the database.
    make_file_read_only(uri);
  }
625
626
627
628
629
630
631
632

  return uri;
}

void ImageGlom::open_with(const Glib::RefPtr<Gio::AppInfo>& app_info)
{
  const Glib::ustring uri = save_to_temp_file();
  if(uri.empty())
633
634
635
636
    return;

  if(app_info)
  {
Murray Cumming's avatar
Murray Cumming committed
637
    app_info->launch_uri(uri); //TODO: Get a GdkAppLaunchContext?
638
639
640
641
642
643
644
645
  }
  else
  {
    //TODO: Avoid duplication in xsl_utils.cc, by moving this into a utility function:  
#ifdef G_OS_WIN32
    // gtk_show_uri doesn't seem to work on Win32, at least not for local files
    // We use Windows API instead.
    // TODO: Check it again and file a bug if necessary.
646
647
    // TODO: and this might not be necessary with Gio::AppInfo::launch_default_for_uri().
    //   Previously we used gtk_show_uri().
648
649
    ShellExecute(0, "open", uri.c_str(), 0, 0, SW_SHOW);
#else
650
    Gio::AppInfo::launch_default_for_uri(uri);
651
652
653
#endif //G_OS_WIN32
  }
}
654

655

656
static void set_file_filter_images(Gtk::FileChooser& file_chooser)
657
{
658
  //Get image formats only:
659
660
661
  Glib::RefPtr<Gtk::FileFilter> filter = Gtk::FileFilter::create();
  filter->set_name(_("Images"));
  filter->add_pixbuf_formats();
662
  file_chooser.add_filter(filter);
663
  
664
665
  ev_document_factory_add_filters(GTK_WIDGET(file_chooser.gobj()), 0);
  
666
667
668
  //Make Images the currently-selected one:
  file_chooser.set_filter(filter);
  
669
  /*  ev_document_factory_add_filters() add this already:
670
671
672
673
  filter = Gtk::FileFilter::create();
  filter->set_name(_("All Files"));
  filter->add_pattern("*");
  file_chooser.add_filter(filter);
674
  */
675
676
}

677
void ImageGlom::on_menupopup_activate_save_file()
678
{
679
  AppWindow* pApp = get_appwindow();
680
681
682
683
684

  Gtk::FileChooserDialog dialog(_("Save Image"), Gtk::FILE_CHOOSER_ACTION_SAVE);
  if(pApp)
    dialog.set_transient_for(*pApp);
          
685
  set_file_filter_images(dialog);
686

687
688
  dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
  dialog.add_button(_("_Save"), Gtk::RESPONSE_OK);
689
690
691
692
693
694
695
696
697
698
699
700
  const int response = dialog.run();
  dialog.hide();
  if(response != Gtk::RESPONSE_OK)
    return;
    
  const Glib::ustring uri = dialog.get_uri();
  if(uri.empty())
    return;
    
  save_file(uri);
}

701
702
703
704
705
706
707
bool ImageGlom::save_file_sync(const Glib::ustring& uri)
{
  //TODO: We should still do this asynchronously, 
  //even when we don't use the dialog's run() to do that
  //because we don't want to offer feedback.
  //Ideally, EvView would just load from data anyway.
  
708
  const GdaBinary* gda_binary = get_binary();
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
  if(!gda_binary)
  {
    std::cerr << G_STRFUNC << ": GdaBinary is null" << std::endl;
    return false;
  }
    
  if(!gda_binary->data)
  {
    std::cerr << G_STRFUNC << ": GdaBinary::data is null" << std::endl;
    return false;
  }

  try
  {
    const std::string filepath = Glib::filename_from_uri(uri);
    Glib::file_set_contents(filepath, (const char*)gda_binary->data, gda_binary->binary_length);
  }
  catch(const Glib::Error& ex)
  {
    std::cerr << G_STRFUNC << "Exception: " << ex.what() << std::endl;
    return false;
  }
  
  return true;
}

735
736
737
738
739
740
741
742
743
744
bool ImageGlom::save_file(const Glib::ustring& uri)
{
  DialogImageSaveProgress* dialog_save = 0;
  Utils::get_glade_widget_derived_with_warning(dialog_save);
  if(!dialog_save)
    return false;
    
  // Automatically delete the dialog when we no longer need it:
  std::auto_ptr<Gtk::Dialog> dialog_keeper(dialog_save);

745
  AppWindow* pApp = get_appwindow();
746
747
748
  if(pApp)
    dialog_save->set_transient_for(*pApp);

749
  const GdaBinary* gda_binary = get_binary();
750
751
752
753
  if(!gda_binary)
    return false;

  dialog_save->set_image_data(*gda_binary);
754
755
  dialog_save->save(uri);

756
  dialog_save->run();
757

758
759
760
  return true;
}

761
void ImageGlom::on_menupopup_activate_select_file()
762
763
764
765
{
  if(m_read_only)
    return;
    
766
  AppWindow* pApp = get_appwindow();
767
768
769
770
771

  Gtk::FileChooserDialog dialog(_("Choose Image"), Gtk::FILE_CHOOSER_ACTION_OPEN);
  if(pApp)
    dialog.set_transient_for(*pApp);
          
772
  set_file_filter_images(dialog);
773

774
  dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
775
776
777
  dialog.add_button(_("Select"), Gtk::RESPONSE_OK);
  int response = dialog.run();
  dialog.hide();
778

779
  if((response != Gtk::RESPONSE_CANCEL) && (response != Gtk::RESPONSE_DELETE_EVENT))
780
  {
781
782
    const Glib::ustring uri = dialog.get_uri();
    if(!uri.empty())
783
    {
784
      DialogImageLoadProgress* dialog;
785
      Utils::get_glade_widget_derived_with_warning(dialog);
786
      if(dialog)
787
      {
788
789
        // 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
790

791
792
793
        if(pApp)
          dialog->set_transient_for(*pApp);

794
        dialog->load(uri);
Armin Burgmeier's avatar
Armin Burgmeier committed
795

796
        if(dialog->run() == Gtk::RESPONSE_ACCEPT)
Armin Burgmeier's avatar
Armin Burgmeier committed
797
        {
798
799
800
801
802
803
          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();
804
805
806
          
          g_value_unset(m_original_data.gobj());
          g_value_init(m_original_data.gobj(), GDA_TYPE_BINARY);
807
808
          gda_value_take_binary(m_original_data.gobj(), bin);

809
          show_image_data();
Armin Burgmeier's avatar
Armin Burgmeier committed
810
811
          signal_edited().emit();
        }
812
813
      }
    }
814
  }
815
816
}

817
818
819
820
821
822
823
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(); 

824
825
826
827
828
829
830
  const Glib::ustring mime_type = get_mime_type();
  if(mime_type.empty())
  {
    std::cerr << G_STRFUNC << ": mime_type is empty." << std::endl;
  }
  
  if(target == mime_type)
831
  {
832
    const GdaBinary* gda_binary = get_binary();
833
834
835
836
837
838
839
840
    if(!gda_binary)
      return;
    
    if(!gda_binary->data)
      return;
    
    selection_data.set(mime_type, 8, gda_binary->data, gda_binary->binary_length);

841
    // This set() override uses an 8-bit text format for the data.
842
    //selection_data.set_pixbuf(m_pixbuf_clipboard);
843
844
845
  }
  else
  {
846
    std::cout << "ExampleWindow::on_clipboard_get(): Unexpected clipboard target format. expected: " << mime_type << std::endl;
847
  }
848
849
850
851
}

void ImageGlom::on_clipboard_clear()
{
852
853
854
  if(m_read_only)
    return;

855
  m_pixbuf_clipboard.reset();
856
857
}

858
void ImageGlom::on_menupopup_activate_copy()
859
{
860
861
862
863
864
865
  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
866
    m_pixbuf_clipboard.reset();
867

868
869
870
  Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get();

  //Targets:
871
872
873
874
875
876
  const Glib::ustring mime_type = get_mime_type();
  if(mime_type.empty())
  {
    std::cerr << G_STRFUNC << ": mime_type is empty." << std::endl;
  }
  
877
  std::vector<Gtk::TargetEntry> listTargets;
878
  listTargets.push_back( Gtk::TargetEntry(mime_type) );
879

880
881
  refClipboard->set( listTargets, sigc::mem_fun(*this, &ImageGlom::on_clipboard_get), sigc::mem_fun(*this, &ImageGlom::on_clipboard_clear) );
}
882

883
884
void ImageGlom::on_clipboard_received_image(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
885
886
887
  if(m_read_only)
    return;

888
889
  if(pixbuf)
  {
Armin Burgmeier's avatar
Armin Burgmeier committed
890
891
892
    // Clear original data of previous image
    m_original_data = Gnome::Gda::Value();

893
    m_pixbuf_original = pixbuf;
894
    show_image_data();
895

896
897
    signal_edited().emit();
  }
898
899
}

900

901
void ImageGlom::on_menupopup_activate_paste()
902
{
903
904
905
  if(m_read_only)
    return;

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

909
910
  if(refClipboard)
    refClipboard->request_image( sigc::mem_fun(*this, &ImageGlom::on_clipboard_received_image) );
911
912
}

913
void ImageGlom::on_menupopup_activate_clear()
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
914
{
915
916
917
  if(m_read_only)
    return;

918
  m_original_data = Gnome::Gda::Value();
919
  show_image_data();
920
  signal_edited().emit();
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
921
922
}

923
924
void ImageGlom::setup_menu_usermode()
{
925
926
  //Create the Gio::ActionGroup and associate it with this widget:
  m_refActionGroup_UserModePopup = Gio::SimpleActionGroup::create();
927

928
  m_refActionOpenFile = m_refActionGroup_UserModePopup->add_action("openfile",
929
930
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file) );

931
  m_refActionOpenFileWith = m_refActionGroup_UserModePopup->add_action("openfilewith",
932
933
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file_with) );
    
934
  m_refActionSaveFile = m_refActionGroup_UserModePopup->add_action("savefile",
935
936
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_save_file) );
    
937
  m_refActionSelectFile = m_refActionGroup_UserModePopup->add_action("selectfile",
938
939
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_select_file) );

940
  m_refActionCopy = m_refActionGroup_UserModePopup->add_action("copy",
941
942
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_copy) );

943
  m_refActionPaste = m_refActionGroup_UserModePopup->add_action("paste",
944
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_paste) );
945

946
  m_refActionClear = m_refActionGroup_UserModePopup->add_action("clear",
Murray Cumming's avatar
0.8.34:    
Murray Cumming committed
947
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_clear) );
948

949
  insert_action_group("imagecontext", m_refActionGroup_UserModePopup);
950
951


952
953
954
  //Create the UI for the menu whose items will activate the actions,
  //when this UI (a GtkMenu) is added and shown:
  m_refBuilder_UserModePopup = Gtk::Builder::create();
955
956
957
958

  try
  {
    Glib::ustring ui_info = 
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
        "<interface>"
        "  <menu id='ContextMenu_UserMode'>"
        "    <section>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Open File</attribute>"
        "        <attribute name='action'>imagecontext.openfile</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>Open File With</attribute>"
        "        <attribute name='action'>imagecontext.openfilewith</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>Select File</attribute>"
        "        <attribute name='action'>imagecontext.selectfile</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Copy</attribute>"
        "        <attribute name='action'>imagecontext.copy</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Paste</attribute>"
        "        <attribute name='action'>imagecontext.paste</attribute>"
        "      </item>"
        "      <item>"
        "        <attribute name='label' translatable='yes'>_Clear</attribute>"
        "        <attribute name='action'>imagecontext.clear</attribute>"
        "      </item>"
        "    </section>"
        "  </menu>"
        "</interface>";

    m_refBuilder_UserModePopup->add_from_string(ui_info);
991
992
993
  }
  catch(const Glib::Error& ex)
  {
994
    std::cerr << G_STRFUNC << ": building menus failed: " <<  ex.what();
995
996
997
  }

  //Get the menu:
998
999
1000
1001
1002
1003
1004
1005
  Glib::RefPtr<Glib::Object> object =
    m_refBuilder_UserModePopup->get_object("ContextMenu_UserMode");
  Glib::RefPtr<Gio::Menu> gmenu =
    Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
  if(!gmenu)
    g_warning("GMenu not found");

  m_pMenuPopup_UserMode = new Gtk::Menu(gmenu);
1006
}
1007
1008
1009

void ImageGlom::do_choose_image()
{
1010
  on_menupopup_activate_select_file();
1011
1012
1013
1014
1015
1016
1017
}

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

1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
void ImageGlom::popup_menu(guint button, guint32 activate_time)
{
  if(!m_pMenuPopup_UserMode)
  {
    std::cerr << G_STRFUNC << ": m_pMenuPopup_UserMode is null" << std::endl;
    return;
  }

  if(!m_pMenuPopup_UserMode->get_attach_widget())
  {
    m_pMenuPopup_UserMode->attach_to_widget(*this);
  }

  m_pMenuPopup_UserMode->popup(button, activate_time);

  m_refActionSelectFile->set_enabled();
}

1036
1037

} //namespace Glom