imageglom.cc 26 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>
Murray Cumming's avatar
Murray Cumming committed
26
#include <libglom/algorithms_utils.h>
27
#include <libglom/data_structure/glomconversions.h>
28
29
#include <glom/utility_widgets/dialog_image_load_progress.h>
#include <glom/utility_widgets/dialog_image_save_progress.h>
30
31
#include <gtkmm/appchooserdialog.h>
#include <gtkmm/filechooserdialog.h>
32
33
#include <giomm/file.h>
#include <giomm/contenttype.h>
34
#include <giomm/menu.h>
35
#include <libgda/gda-blob-op.h>
36
#include <glibmm/convert.h>
37

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

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

44
45
46
namespace Glom
{

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

50
ImageGlom::ImageGlom()
51
52
: m_ev_scrolled_window(nullptr),
  m_ev_view(nullptr),
53
  m_ev_document_model(nullptr)
54
{
55
56
57
  init();
}

58
ImageGlom::ImageGlom(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& /* builder */)
59
: Gtk::EventBox(cobject),
60
  m_ev_scrolled_window(nullptr),
Murray Cumming's avatar
Murray Cumming committed
61
  m_ev_view(nullptr),
62
  m_ev_document_model(nullptr)
63
64
65
66
67
68
{
  init();
}

void ImageGlom::init()
{
69
  //TODO: Don't instantiate this unnecessarily.
70
  m_ev_view = EV_VIEW(ev_view_new());
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.
Murray Cumming's avatar
Murray Cumming committed
77
  auto cppEvView = Glib::wrap(GTK_WIDGET(m_ev_view));
78
79
  cppEvView->signal_button_press_event().connect(
    sigc::mem_fun(*this, &ImageGlom::on_button_press_event), false);
80

81
82
  m_read_only = false;

83
#ifndef GLOM_ENABLE_CLIENT_ONLY
84
  setup_menu(this);
85
86
#endif // !GLOM_ENABLE_CLIENT_ONLY

87
  setup_menu_usermode();
88

89
  //m_image.set_size_request(150, 150);
90

91

92
93
  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();
94

95
  add(m_frame);
96
97
}

98
99


100
101
102
103
ImageGlom::~ImageGlom()
{
}

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

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

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

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

153
154
155
        return true; //We handled this event.
      }
    }
156

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

163
164
165
    }
  }

166
  return Gtk::EventBox::on_button_press_event(button_event);
167
168
}

169
AppWindow* ImageGlom::get_appwindow() const
170
{
Murray Cumming's avatar
Murray Cumming committed
171
  auto pWindow = const_cast<Gtk::Container*>(get_toplevel());
172
173
  //TODO: This only works when the child widget is already in its parent.

174
  return dynamic_cast<AppWindow*>(pWindow);
175
176
}

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

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

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

Gnome::Gda::Value ImageGlom::get_value() const
{
200
  return m_original_data;
201
}
202

203
void ImageGlom::on_size_allocate(Gtk::Allocation& allocation)
204
{
205
  Gtk::EventBox::on_size_allocate(allocation);
206

207
  //Resize the GtkImage if necessary:
Murray Cumming's avatar
Murray Cumming committed
208
  if(m_pixbuf_original)
209
  {
Murray Cumming's avatar
Murray Cumming committed
210
    const auto pixbuf_scaled = get_scaled_image();
211
212
    m_image.set(pixbuf_scaled);
  }
213
214
}

215
static void image_glom_ev_job_finished(EvJob* job, void* user_data)
216
{
217
218
  g_assert(job);
  
Murray Cumming's avatar
Murray Cumming committed
219
  auto self = static_cast<ImageGlom*>(user_data);
220
221
222
223
224
225
226
  g_assert(self);
  
  self->on_ev_job_finished(job);
}
  
void ImageGlom::on_ev_job_finished(EvJob* job)
{
227
  if(ev_job_is_failed (job)) {
Murray Cumming's avatar
Murray Cumming committed
228
229
230
231
232
233
234
235
236
237
238
239
    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);
240
}
241

242
243
const GdaBinary* ImageGlom::get_binary() const
{
Murray Cumming's avatar
Murray Cumming committed
244
  const GdaBinary* gda_binary = nullptr;
245
246
  if(m_original_data.get_value_type() == GDA_TYPE_BINARY)
    gda_binary = gda_value_get_binary(m_original_data.gobj());
247
248
  else if(m_original_data.get_value_type() == GDA_TYPE_BLOB)
  {
Murray Cumming's avatar
Murray Cumming committed
249
    const auto gda_blob = gda_value_get_blob(m_original_data.gobj());
250
251
252
    if(gda_blob && gda_blob_op_read_all(const_cast<GdaBlobOp*>(gda_blob->op), const_cast<GdaBlob*>(gda_blob)))
      gda_binary = &(gda_blob->data);
  }
253
254
255
256
  
  return gda_binary;
}

257
258
Glib::ustring ImageGlom::get_mime_type() const
{
Murray Cumming's avatar
Murray Cumming committed
259
  const auto gda_binary = get_binary();
260

261
262
263
264
265
266
267
  if(!gda_binary)
    return Glib::ustring();
    
  if(!gda_binary->data)
    return Glib::ustring();

  bool uncertain = false;
Murray Cumming's avatar
Murray Cumming committed
268
  const auto result = Gio::content_type_guess(std::string(),
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
    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.  
Murray Cumming's avatar
Murray Cumming committed
285
  auto types_list = ev_backends_manager_get_all_types_info();
286
287
288
289
290
291
292
  if(!types_list)
  {
    return;
  }
  
  for(GList* l = types_list; l; l = g_list_next(l))
  {
Murray Cumming's avatar
Murray Cumming committed
293
294
295
296
    EvTypeInfo *info = (EvTypeInfo *)l->data;
    if(!info)
      continue;

Murray Cumming's avatar
Murray Cumming committed
297
    const char* mime_type = nullptr;
Murray Cumming's avatar
Murray Cumming committed
298
299
300
301
    int i = 0;
    while((mime_type = info->mime_types[i++]))
    {
      if(mime_type)
302
        m_evince_supported_mime_types.emplace_back(mime_type);
Murray Cumming's avatar
Murray Cumming committed
303
304
305
      //std::cout << "evince supported mime_type=" << mime_type << std::endl; 
    }
  }  
306
307
}

308
309
310
311
312
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;
Murray Cumming's avatar
Murray Cumming committed
313

314
  for(const auto& format : Gdk::Pixbuf::get_formats())
315
  {
Murray Cumming's avatar
Murray Cumming committed
316
    const auto mime_types = format.get_mime_types();
317
318
319
320
321
322
    m_gdkpixbuf_supported_mime_types.insert(
      m_gdkpixbuf_supported_mime_types.end(),
      mime_types.begin(), mime_types.end());
  }
}

323
324
325
326
void ImageGlom::show_image_data()
{
  bool use_evince = false;
  
Murray Cumming's avatar
Murray Cumming committed
327
  const auto mime_type = get_mime_type();
328
329

  //std::cout << "mime_type=" << mime_type << std::endl; 
330
  
331
  fill_evince_supported_mime_types();
Murray Cumming's avatar
Murray Cumming committed
332
  if(Utils::find_exists(m_evince_supported_mime_types, mime_type))
333
  {
334
335
    use_evince = true;
  }  
336
  
337
338
  m_frame.remove();
    
339
340
  //Clear all possible display widgets:
  m_pixbuf_original.reset();
341
  m_image.set(Glib::RefPtr<Gdk::Pixbuf>()); //TODO: Add an unset() to gtkmm.
342
343
344
345
  
  if(m_ev_document_model)
  {
    g_object_unref(m_ev_document_model);
Murray Cumming's avatar
Murray Cumming committed
346
    m_ev_document_model = nullptr;
347
348
349
350
351
352
  }

  if(use_evince)
  {
    //Use EvView:
    m_image.hide();
353
    
354
    gtk_widget_show(GTK_WIDGET(m_ev_view));
355
356
357
358
359
360
361
362
363
364

    if (!m_ev_scrolled_window)
    {
      m_ev_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow());
      m_ev_scrolled_window->show();
      gtk_container_add(GTK_CONTAINER(m_ev_scrolled_window->gobj()), GTK_WIDGET(m_ev_view));
    }

    m_frame.add(*m_ev_scrolled_window);

365

366
367
368
    // Try loading from data in memory:
    // TODO: Uncomment this if this API is added: https://bugzilla.gnome.org/show_bug.cgi?id=654832
    /*
Murray Cumming's avatar
Murray Cumming committed
369
    const auto gda_binary = get_binary();
370
371
    if(!gda_binary || !gda_binary->data || !gda_binary->binary_length)
    {
372
       std::cerr << G_STRFUNC << "Data was null or empty.\n";
373
374
375
376
377
378
379
380
      return;
    }
    
    EvJob *job = ev_job_load_new_with_data(
      (char*)gda_binary->data, gda_binary->binary_length);
    */
    //TODO: Test failure asynchronously.
    
Murray Cumming's avatar
Murray Cumming committed
381
    const auto uri = save_to_temp_file(false /* don't show progress */);
382
383
    if(uri.empty())
    {
384
      std::cerr << G_STRFUNC << "Could not save temp file to show in the EvView.\n";
385
386
387
388
389
390
    }
  
    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);
391
392
393
394
    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
395

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

443
Glib::RefPtr<Gdk::Pixbuf> ImageGlom::get_scaled_image()
444
{
445
  auto pixbuf = m_pixbuf_original;
446
447

  if(!pixbuf)
448
    return pixbuf;
449
 
Murray Cumming's avatar
Murray Cumming committed
450
451
452
  const auto allocation = m_image.get_allocation();
  const auto pixbuf_height = pixbuf->get_height();
  const auto pixbuf_width = pixbuf->get_width();
453
    
Murray Cumming's avatar
Murray Cumming committed
454
455
  const auto allocation_height = allocation.get_height();
  const auto allocation_width = allocation.get_width();
456
      
Murray Cumming's avatar
Murray Cumming committed
457
458
  //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;
459

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

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

499
void ImageGlom::on_menupopup_activate_open_file()
500
{
501
502
503
  open_with();
}

504
void ImageGlom::on_menupopup_activate_open_file_with()
505
{
Murray Cumming's avatar
Murray Cumming committed
506
  auto pApp = get_appwindow();
507
508

  //Offer the user a choice of suitable applications:
Murray Cumming's avatar
Murray Cumming committed
509
  const auto mime_type = get_mime_type();
510
511
  if(mime_type.empty())
  {
512
    std::cerr << G_STRFUNC << ": mime_type is empty.\n";
513
514
515
  }
  
  Gtk::AppChooserDialog dialog(mime_type);
516
517
518
519
  if(pApp)
    dialog.set_transient_for(*pApp);

  if(dialog.run() != Gtk::RESPONSE_OK)
520
    return;
521
  
522
  auto app_info = dialog.get_app_info();
523
524
  if(!app_info)
  {
525
    std::cerr << G_STRFUNC << ": app_info was null.\n";
526
527
528
529
  }
  
  open_with(app_info);
}
530

531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
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())
  {
547
    std::cerr << G_STRFUNC << ": filepath is empty.\n";
548
549
  }
  
Murray Cumming's avatar
Murray Cumming committed
550
  const auto result = chmod(filepath.c_str(), S_IRUSR);
551
552
  if(result != 0)
  {
553
    std::cerr << G_STRFUNC << ": chmod() failed.\n";
554
555
556
557
558
  }
  
  //Setting the attribute via gio gives us this exception:
  //"Setting attribute access::can-write not supported"
  /*
559
  auto file = Gio::File::create_for_uri(uri);
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574

  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)
  {
575
    std::cerr << G_STRFUNC << ": : file_info is null\n";
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
    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;
  }
  */
}

597
Glib::ustring ImageGlom::save_to_temp_file(bool show_progress)
598
{
599
600
  Glib::ustring uri = Utils::get_temp_file_uri("glom_image");
  if(uri.empty())
601
  {
602
    std::cerr << G_STRFUNC << ": : uri is empty.\n";
603
  }
604
  
605
606
607
608
609
  bool saved = false;
  if(show_progress)
    saved = save_file(uri);
  else
    saved = save_file_sync(uri);
610
  
611
612
613
  if(!saved)
  {
    uri = Glib::ustring();
614
    std::cerr << G_STRFUNC << ": save_file() failed.\n";
615
  }
616
617
618
619
620
621
622
  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);
  }
623
624
625
626
627
628

  return uri;
}

void ImageGlom::open_with(const Glib::RefPtr<Gio::AppInfo>& app_info)
{
Murray Cumming's avatar
Murray Cumming committed
629
  const auto uri = save_to_temp_file();
630
  if(uri.empty())
631
632
633
634
    return;

  if(app_info)
  {
Murray Cumming's avatar
Murray Cumming committed
635
    app_info->launch_uri(uri); //TODO: Get a GdkAppLaunchContext?
636
637
638
639
640
641
642
643
  }
  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.
644
645
    // TODO: and this might not be necessary with Gio::AppInfo::launch_default_for_uri().
    //   Previously we used gtk_show_uri().
646
647
    ShellExecute(0, "open", uri.c_str(), 0, 0, SW_SHOW);
#else
648
    Gio::AppInfo::launch_default_for_uri(uri);
649
650
651
#endif //G_OS_WIN32
  }
}
652

653

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

675
void ImageGlom::on_menupopup_activate_save_file()
676
{
Murray Cumming's avatar
Murray Cumming committed
677
  auto pApp = get_appwindow();
678
679
680
681
682

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

685
686
  dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
  dialog.add_button(_("_Save"), Gtk::RESPONSE_OK);
Murray Cumming's avatar
Murray Cumming committed
687
  const auto response = dialog.run();
688
689
690
691
  dialog.hide();
  if(response != Gtk::RESPONSE_OK)
    return;
    
Murray Cumming's avatar
Murray Cumming committed
692
  const auto uri = dialog.get_uri();
693
694
695
696
697
698
  if(uri.empty())
    return;
    
  save_file(uri);
}

699
700
701
702
703
704
705
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.
  
Murray Cumming's avatar
Murray Cumming committed
706
  const auto gda_binary = get_binary();
707
708
  if(!gda_binary)
  {
709
    std::cerr << G_STRFUNC << ": GdaBinary is null\n";
710
711
712
713
714
    return false;
  }
    
  if(!gda_binary->data)
  {
715
    std::cerr << G_STRFUNC << ": GdaBinary::data is null\n";
716
717
718
719
720
    return false;
  }

  try
  {
Murray Cumming's avatar
Murray Cumming committed
721
    const auto filepath = Glib::filename_from_uri(uri);
722
723
724
725
726
727
728
729
730
731
732
    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;
}

733
734
bool ImageGlom::save_file(const Glib::ustring& uri)
{
Murray Cumming's avatar
Murray Cumming committed
735
  DialogImageSaveProgress* dialog_save = nullptr;
736
737
738
739
740
  Utils::get_glade_widget_derived_with_warning(dialog_save);
  if(!dialog_save)
    return false;
    
  // Automatically delete the dialog when we no longer need it:
741
  std::shared_ptr<Gtk::Dialog> dialog_keeper(dialog_save);
742

Murray Cumming's avatar
Murray Cumming committed
743
  auto pApp = get_appwindow();
744
745
746
  if(pApp)
    dialog_save->set_transient_for(*pApp);

Murray Cumming's avatar
Murray Cumming committed
747
  const auto gda_binary = get_binary();
748
749
750
751
  if(!gda_binary)
    return false;

  dialog_save->set_image_data(*gda_binary);
752
753
  dialog_save->save(uri);

754
  dialog_save->run();
755

756
757
758
  return true;
}

759
void ImageGlom::on_menupopup_activate_select_file()
760
761
762
763
{
  if(m_read_only)
    return;
    
Murray Cumming's avatar
Murray Cumming committed
764
  auto pApp = get_appwindow();
765
766
767
768
769

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

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

777
  if((response != Gtk::RESPONSE_CANCEL) && (response != Gtk::RESPONSE_DELETE_EVENT))
778
  {
Murray Cumming's avatar
Murray Cumming committed
779
    const auto uri = dialog.get_uri();
780
    if(!uri.empty())
781
    {
Murray Cumming's avatar
Murray Cumming committed
782
783
784
      DialogImageLoadProgress* dialog_progress = nullptr;
      Utils::get_glade_widget_derived_with_warning(dialog_progress);
      if(dialog_progress)
785
      {
786
        // Automatically delete the dialog when we no longer need it:
Murray Cumming's avatar
Murray Cumming committed
787
        std::shared_ptr<Gtk::Dialog> dialog_keeper(dialog_progress);
Armin Burgmeier's avatar
Armin Burgmeier committed
788

789
        if(pApp)
Murray Cumming's avatar
Murray Cumming committed
790
          dialog_progress->set_transient_for(*pApp);
791

Murray Cumming's avatar
Murray Cumming committed
792
        dialog_progress->load(uri);
Armin Burgmeier's avatar
Armin Burgmeier committed
793

Murray Cumming's avatar
Murray Cumming committed
794
        if(dialog_progress->run() == Gtk::RESPONSE_ACCEPT)
Armin Burgmeier's avatar
Armin Burgmeier committed
795
        {
796
          GdaBinary* bin = g_new(GdaBinary, 1);
797
          auto image_data = dialog_progress->get_image_data();
798
799
800
801
          bin->data = image_data->data;
          bin->binary_length = image_data->binary_length;

          m_original_data = Gnome::Gda::Value();
802
803
804
          
          g_value_unset(m_original_data.gobj());
          g_value_init(m_original_data.gobj(), GDA_TYPE_BINARY);
805
806
          gda_value_take_binary(m_original_data.gobj(), bin);

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

815
816
817
818
819
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.

Murray Cumming's avatar
Murray Cumming committed
820
  const auto target = selection_data.get_target(); 
821

Murray Cumming's avatar
Murray Cumming committed
822
  const auto mime_type = get_mime_type();
823
824
  if(mime_type.empty())
  {
825
    std::cerr << G_STRFUNC << ": mime_type is empty.\n";
826
827
828
  }
  
  if(target == mime_type)
829
  {
Murray Cumming's avatar
Murray Cumming committed
830
    const auto gda_binary = get_binary();
831
832
833
834
835
836
837
838
    if(!gda_binary)
      return;
    
    if(!gda_binary->data)
      return;
    
    selection_data.set(mime_type, 8, gda_binary->data, gda_binary->binary_length);

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

void ImageGlom::on_clipboard_clear()
{
850
851
852
  if(m_read_only)
    return;

853
  m_pixbuf_clipboard.reset();
854
855
}

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

866
  auto refClipboard = Gtk::Clipboard::get();
867
868

  //Targets:
Murray Cumming's avatar
Murray Cumming committed
869
  const auto mime_type = get_mime_type();
870
871
  if(mime_type.empty())
  {
872
    std::cerr << G_STRFUNC << ": mime_type is empty.\n";
873
874
  }
  
875
  std::vector<Gtk::TargetEntry> listTargets;
876
  listTargets.emplace_back( Gtk::TargetEntry(mime_type) );
877

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

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

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

891
    m_pixbuf_original = pixbuf;
892
    show_image_data();
893

894
895
    signal_edited().emit();
  }
896
897
}

898

899
void ImageGlom::on_menupopup_activate_paste()
900
{
901
902
903
  if(m_read_only)
    return;

904
  //Tell the clipboard to call our method when it is ready:
905
  auto refClipboard = Gtk::Clipboard::get();
906

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

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

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

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

926
  m_refActionOpenFile = m_refActionGroup_UserModePopup->add_action("open-file",
927
928
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file) );

929
  m_refActionOpenFileWith = m_refActionGroup_UserModePopup->add_action("open-fil-ewith",
930
931
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file_with) );
    
932
  m_refActionSaveFile = m_refActionGroup_UserModePopup->add_action("save-file",
933
934
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_save_file) );
    
935
  m_refActionSelectFile = m_refActionGroup_UserModePopup->add_action("select-file",
936
937
    sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_select_file) );

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

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

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

947
  insert_action_group("imagecontext", m_refActionGroup_UserModePopup);
948
949


950
951
  //Create the UI for the menu whose items will activate the actions,
  //when this UI (a GtkMenu) is added and shown:
952

953
  auto menu = Gio::Menu::create();
954
955
  menu->append(_("_Open File"), "context.open-file");
  menu->append(_("Open File With"), "context.open-file-with");
956
  menu->append(_("Select File"), "context.select-file");
957
958
959
  menu->append(_("_Copy"), "context.copy");
  menu->append(_("_Paste"), "context.paste");
  menu->append(_("_Clear"), "context.clear");
960

961
  m_pMenuPopup_UserMode = std::make_unique<Gtk::Menu>(menu);