imageglom.cc 25.8 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>
Murray Cumming's avatar
Murray Cumming committed
30
#include <libglom/utils.h>
31
#include <libglom/file_utils.h>
32 33
#include <gtkmm/appchooserdialog.h>
#include <gtkmm/filechooserdialog.h>
34
#include <giomm/contenttype.h>
35
#include <giomm/menu.h>
36
#include <libgda/gda-blob-op.h>
37
#include <glibmm/convert.h>
38

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

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

45 46 47
namespace Glom
{

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

51
ImageGlom::ImageGlom()
52 53
: m_ev_view(nullptr),
  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),
Murray Cumming's avatar
Murray Cumming committed
60
  m_ev_view(nullptr),
61
  m_ev_document_model(nullptr)
62 63 64 65
{
  init();
}

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
void ImageGlom::clear_image_from_widgets()
{
  if(m_image)
  {
    m_image->set(Glib::RefPtr<Gdk::Pixbuf>()); //TODO: Add an unset() to gtkmm.
  }

  if(m_ev_document_model)
  {
    g_object_unref(m_ev_document_model);
    m_ev_document_model = nullptr;
  }
}

void ImageGlom::init_widgets(bool use_evince)
81
{
82
  clear_image_from_widgets();
83

84
  m_frame.remove();
85

86
  Gtk::Widget* widget = nullptr;
87

88 89 90 91 92 93 94
  if(use_evince)
  {
    if(!m_ev_view)
    {
      m_ev_view = EV_VIEW(ev_view_new());
      gtk_widget_show(GTK_WIDGET(m_ev_view));

95
      m_ev_scrolled_window = std::make_unique<Gtk::ScrolledWindow>();
96 97 98 99
      gtk_container_add(GTK_CONTAINER(m_ev_scrolled_window->gobj()), GTK_WIDGET(m_ev_view));

      //gtk_widget_add_events(GTK_WIDGET(m_ev_view), GDK_BUTTON_PRESS_MASK);

Murray Cumming's avatar
Murray Cumming committed
100
      //Connect the the EvView's button-press-event signal,
101 102 103 104 105 106 107
      //because we don't get it otherwise.
      //For some reason this is not necessary with the GtkImage.
      auto cppEvView = Glib::wrap(GTK_WIDGET(m_ev_view));
      cppEvView->signal_button_press_event().connect(
        sigc::mem_fun(*this, &ImageGlom::on_button_press_event), false);
    }

108
    m_image.reset();
109

110
    widget = m_ev_scrolled_window.get();
111 112 113
  }
  else
  {
114
    m_image = std::make_unique<Gtk::Image>();
115 116 117 118
    if(m_ev_view)
    {
      gtk_widget_destroy(GTK_WIDGET(m_ev_view));
      m_ev_view = nullptr;
119
      m_ev_scrolled_window.reset();
120 121
    }

122
    widget = m_image.get();
123 124 125 126 127 128 129 130
  }

  widget->show();
  m_frame.add(*widget);
}

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

133
#ifndef GLOM_ENABLE_CLIENT_ONLY
134
  setup_menu(this);
135 136
#endif // !GLOM_ENABLE_CLIENT_ONLY

137
  setup_menu_usermode();
138

139 140
  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();
141

142
  add(m_frame);
143 144
}

145
void ImageGlom::set_layout_item(const std::shared_ptr<LayoutItem>& layout_item, const Glib::ustring& table_name)
146 147
{
  LayoutWidgetField::set_layout_item(layout_item, table_name);
148
#ifdef GTKMM_ATKMM_ENABLED
149
  get_accessible()->set_name(layout_item->get_name());
Murray Cumming's avatar
Murray Cumming committed
150
#endif
151
}
152

153
bool ImageGlom::on_button_press_event(GdkEventButton *button_event)
154 155
{
  GdkModifierType mods;
Murray Cumming's avatar
Murray Cumming committed
156
  gdk_window_get_device_position( gtk_widget_get_window (Gtk::Widget::gobj()), button_event->device, nullptr, nullptr, &mods );
157

158
  //Enable/Disable items.
159
  //We did this earlier, but get_appwindow is more likely to work now:
Murray Cumming's avatar
Murray Cumming committed
160
  auto pApp = get_appwindow();
161 162
  if(pApp)
  {
163
#ifndef GLOM_ENABLE_CLIENT_ONLY
164 165 166 167
    pApp->add_developer_action(m_context_layout); //So that it can be disabled when not in developer mode.
    pApp->add_developer_action(m_context_add_field);
    pApp->add_developer_action(m_context_add_related_records);
    pApp->add_developer_action(m_context_add_group);
168 169

    pApp->update_userlevel_ui(); //Update our action's sensitivity.
170
#endif // !GLOM_ENABLE_CLIENT_ONLY
171 172 173

    //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.
174
#ifndef GLOM_ENABLE_CLIENT_ONLY
175
    if(pApp->get_userlevel() == AppState::userlevels::DEVELOPER)
176 177 178 179
    {
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
180
        popup_menu(button_event);
Murray Cumming's avatar
Murray Cumming committed
181

182 183 184
        return true; //We handled this event.
      }
    }
185
    else
186
#endif // !GLOM_ENABLE_CLIENT_ONLY
187
    {
188
      // We cannot be in developer mode in client only mode.
189 190 191
      if(mods & GDK_BUTTON3_MASK)
      {
        //Give user choices of actions on this item:
192
        popup_menu(button_event);
193

194 195 196
        return true; //We handled this event.
      }
    }
197

198 199
    //Single-click to select file:
    if(mods & GDK_BUTTON1_MASK)
200
    {
201
      on_menupopup_activate_select_file();
202 203
      return true; //We handled this event.

204 205 206
    }
  }

207
  return Gtk::EventBox::on_button_press_event(button_event);
208 209
}

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

215
  return dynamic_cast<AppWindow*>(pWindow);
216 217
}

218
/*
219 220 221
void ImageGlom::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
{
  m_pixbuf_original = pixbuf;
222
  show_image_data();
223
}
224
*/
225

226
void ImageGlom::set_value(const Gnome::Gda::Value& value)
227
{
Murray Cumming's avatar
Murray Cumming committed
228
  // Remember original data
229
  clear_original_data();
Armin Burgmeier's avatar
Armin Burgmeier committed
230
  m_original_data = value;
231
  show_image_data();
232 233 234 235
}

Gnome::Gda::Value ImageGlom::get_value() const
{
236
  return m_original_data;
237
}
238

239
void ImageGlom::on_size_allocate(Gtk::Allocation& allocation)
240
{
241
  Gtk::EventBox::on_size_allocate(allocation);
242

243
  //Resize the GtkImage if necessary:
Murray Cumming's avatar
Murray Cumming committed
244
  if(m_pixbuf_original)
245
  {
Murray Cumming's avatar
Murray Cumming committed
246
    const auto pixbuf_scaled = get_scaled_image();
247
    m_image->set(pixbuf_scaled);
248
  }
249 250
}

251
static void image_glom_ev_job_finished(EvJob* job, void* user_data)
252
{
253
  g_assert(job);
Murray Cumming's avatar
Murray Cumming committed
254

Murray Cumming's avatar
Murray Cumming committed
255
  auto self = static_cast<ImageGlom*>(user_data);
256
  g_assert(self);
Murray Cumming's avatar
Murray Cumming committed
257

258 259
  self->on_ev_job_finished(job);
}
Murray Cumming's avatar
Murray Cumming committed
260

261 262
void ImageGlom::on_ev_job_finished(EvJob* job)
{
263
  if(ev_job_is_failed (job)) {
Murray Cumming's avatar
Murray Cumming committed
264 265 266 267 268 269 270 271 272
    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);
Murray Cumming's avatar
Murray Cumming committed
273

Murray Cumming's avatar
Murray Cumming committed
274 275
  //TODO: Show that we are no longer loading.
  //ev_view_set_loading(m_ev_view, FALSE);
276
}
277

278 279
const GdaBinary* ImageGlom::get_binary() const
{
Murray Cumming's avatar
Murray Cumming committed
280
  const GdaBinary* gda_binary = nullptr;
281 282
  if(m_original_data.get_value_type() == GDA_TYPE_BINARY)
    gda_binary = gda_value_get_binary(m_original_data.gobj());
283 284
  else if(m_original_data.get_value_type() == GDA_TYPE_BLOB)
  {
Murray Cumming's avatar
Murray Cumming committed
285
    const auto gda_blob = gda_value_get_blob(m_original_data.gobj());
286
    if(gda_blob && gda_blob_op_read_all(gda_blob->op, const_cast<GdaBlob*>(gda_blob)))
287 288
      gda_binary = &(gda_blob->data);
  }
Murray Cumming's avatar
Murray Cumming committed
289

290 291 292
  return gda_binary;
}

293 294
Glib::ustring ImageGlom::get_mime_type() const
{
Murray Cumming's avatar
Murray Cumming committed
295
  const auto gda_binary = get_binary();
296

297 298
  if(!gda_binary)
    return Glib::ustring();
Murray Cumming's avatar
Murray Cumming committed
299

300 301 302 303
  if(!gda_binary->data)
    return Glib::ustring();

  bool uncertain = false;
Murray Cumming's avatar
Murray Cumming committed
304
  const auto result = Gio::content_type_guess(std::string(),
305 306 307 308
    gda_binary->data, gda_binary->binary_length,
    uncertain);

  //std::cout << G_STRFUNC << ": mime_type=" << result << ", uncertain=" << uncertain << std::endl;
Murray Cumming's avatar
Murray Cumming committed
309
  return result;
310 311 312 313 314 315 316 317 318 319
}

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,
Murray Cumming's avatar
Murray Cumming committed
320
  //but that support was then removed.
Murray Cumming's avatar
Murray Cumming committed
321
  auto types_list = ev_backends_manager_get_all_types_info();
322 323 324 325
  if(!types_list)
  {
    return;
  }
Murray Cumming's avatar
Murray Cumming committed
326

327 328
  for(GList* l = types_list; l; l = g_list_next(l))
  {
Murray Cumming's avatar
Murray Cumming committed
329 330 331 332
    EvTypeInfo *info = (EvTypeInfo *)l->data;
    if(!info)
      continue;

Murray Cumming's avatar
Murray Cumming committed
333
    const char* mime_type = nullptr;
Murray Cumming's avatar
Murray Cumming committed
334 335 336 337
    int i = 0;
    while((mime_type = info->mime_types[i++]))
    {
      if(mime_type)
338
        m_evince_supported_mime_types.emplace_back(mime_type);
Murray Cumming's avatar
Murray Cumming committed
339
      //std::cout << "evince supported mime_type=" << mime_type << std::endl;
Murray Cumming's avatar
Murray Cumming committed
340
    }
Murray Cumming's avatar
Murray Cumming committed
341
  }
342 343
}

344 345 346 347 348
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
349

350
  for(const auto& format : Gdk::Pixbuf::get_formats())
351
  {
Murray Cumming's avatar
Murray Cumming committed
352
    const auto mime_types = format.get_mime_types();
353 354 355 356 357 358
    m_gdkpixbuf_supported_mime_types.insert(
      m_gdkpixbuf_supported_mime_types.end(),
      mime_types.begin(), mime_types.end());
  }
}

359 360 361
void ImageGlom::show_image_data()
{
  bool use_evince = false;
Murray Cumming's avatar
Murray Cumming committed
362

Murray Cumming's avatar
Murray Cumming committed
363
  const auto mime_type = get_mime_type();
364

Murray Cumming's avatar
Murray Cumming committed
365 366
  //std::cout << "mime_type=" << mime_type << std::endl;

367
  fill_evince_supported_mime_types();
Murray Cumming's avatar
Murray Cumming committed
368
  if(Utils::find_exists(m_evince_supported_mime_types, mime_type))
369
  {
370
    use_evince = true;
371
  }
Murray Cumming's avatar
Murray Cumming committed
372

373 374
  init_widgets(use_evince);

375 376 377 378 379
  //Clear all possible display widgets:
  m_pixbuf_original.reset();

  if(use_evince)
  {
380 381 382
    // 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
383
    const auto gda_binary = get_binary();
384 385
    if(!gda_binary || !gda_binary->data || !gda_binary->binary_length)
    {
386
       std::cerr << G_STRFUNC << "Data was null or empty.\n";
387 388
      return;
    }
Murray Cumming's avatar
Murray Cumming committed
389

390 391 392 393
    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
394

Murray Cumming's avatar
Murray Cumming committed
395
    const auto uri = save_to_temp_file(false /* don't show progress */);
396 397
    if(uri.empty())
    {
398
      std::cerr << G_STRFUNC << "Could not save temp file to show in the EvView.\n";
399
    }
Murray Cumming's avatar
Murray Cumming committed
400

401
    EvJob *job = ev_job_load_new(uri.c_str());
Murray Cumming's avatar
Murray Cumming committed
402

403 404
    m_ev_document_model = ev_document_model_new();
    ev_view_set_model(m_ev_view, m_ev_document_model);
405 406 407 408
    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
409

410
    g_signal_connect (job, "finished",
411 412
      G_CALLBACK (image_glom_ev_job_finished), this);
    ev_job_scheduler_push_job (job, EV_JOB_PRIORITY_NONE);
413 414 415 416
  }
  else
  {
    //Use GtkImage instead:
417
    Glib::RefPtr<const Gio::Icon> icon;
Murray Cumming's avatar
Murray Cumming committed
418

419 420
    bool use_gdkpixbuf = false;
    fill_gdkpixbuf_supported_mime_types();
Murray Cumming's avatar
Murray Cumming committed
421
    if(Utils::find_exists(m_gdkpixbuf_supported_mime_types, mime_type))
422 423 424
    {
      use_gdkpixbuf = true;
    }
Murray Cumming's avatar
Murray Cumming committed
425

426 427 428
    if(use_gdkpixbuf)
    {
      //Try to use GdkPixbuf's loader:
429
      m_pixbuf_original = UiUtils::get_pixbuf_for_gda_value(m_original_data);
430 431 432
    }
    else
    {
433 434
      //Get an icon for the file type;
      icon = Gio::content_type_get_icon(mime_type);
435
    }
Murray Cumming's avatar
Murray Cumming committed
436

437
    if(m_pixbuf_original)
438
    {
439
      auto pixbuf_scaled = get_scaled_image();
440
      m_image->set(pixbuf_scaled);
441
    }
442 443
    else if(icon)
    {
444
      m_image->set(icon, Gtk::ICON_SIZE_DIALOG);
445
    }
446
    else
447
    {
448
      m_image->set_from_icon_name("image-missing", Gtk::ICON_SIZE_DIALOG);
449
    }
450 451 452
  }
}

453
Glib::RefPtr<Gdk::Pixbuf> ImageGlom::get_scaled_image()
454
{
455
  auto pixbuf = m_pixbuf_original;
456 457

  if(!pixbuf)
458
    return pixbuf;
Murray Cumming's avatar
Murray Cumming committed
459

460
  const auto allocation = m_image->get_allocation();
Murray Cumming's avatar
Murray Cumming committed
461 462
  const auto pixbuf_height = pixbuf->get_height();
  const auto pixbuf_width = pixbuf->get_width();
Murray Cumming's avatar
Murray Cumming committed
463

Murray Cumming's avatar
Murray Cumming committed
464 465
  const auto allocation_height = allocation.get_height();
  const auto allocation_width = allocation.get_width();
Murray Cumming's avatar
Murray Cumming committed
466

Murray Cumming's avatar
Murray Cumming committed
467 468
  //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;
469

470
  if( (pixbuf_height > allocation_height) ||
471 472
      (pixbuf_width > allocation_width) )
  {
473
    if(true) //allocation_height > 10 || allocation_width > 10)
474
    {
475
      auto pixbuf_scaled = UiUtils::image_scale_keeping_ratio(pixbuf, allocation_height, allocation_width);
Murray Cumming's avatar
Murray Cumming committed
476

477
      //Don't set a new pixbuf if the dimensions have not changed:
478
      Glib::RefPtr<Gdk::Pixbuf> pixbuf_in_image;
479

480 481
      if(m_image->get_storage_type() == Gtk::IMAGE_PIXBUF) //Prevent warning.
        pixbuf_in_image = m_image->get_pixbuf();
482 483

      if( !pixbuf_in_image || !pixbuf_scaled || (pixbuf_in_image->get_height() != pixbuf_scaled->get_height()) || (pixbuf_in_image->get_width() != pixbuf_scaled->get_width()) )
484
      {
Murray Cumming's avatar
Murray Cumming committed
485
        /*
486
        std::cout << "get_scale(): returning scaled\n";
487 488 489 490
        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
491
        */
Murray Cumming's avatar
Murray Cumming committed
492

493
        return pixbuf_scaled;
494
      }
495
      else
496
      {
Murray Cumming's avatar
Murray Cumming committed
497
        //Return the existing one,
498 499 500
        //instead of a new one with the same contents,
        //so no unnecessary changes will be triggered.
        return pixbuf_in_image;
501
      }
502
    }
503
  }
Murray Cumming's avatar
Murray Cumming committed
504

505
  //std::cout << "get_scaled(): returning original\n";
506
  return pixbuf;
507 508
}

509
void ImageGlom::on_menupopup_activate_open_file()
510
{
511 512 513
  open_with();
}

514
void ImageGlom::on_menupopup_activate_open_file_with()
515
{
Murray Cumming's avatar
Murray Cumming committed
516
  auto pApp = get_appwindow();
517 518

  //Offer the user a choice of suitable applications:
Murray Cumming's avatar
Murray Cumming committed
519
  const auto mime_type = get_mime_type();
520 521
  if(mime_type.empty())
  {
522
    std::cerr << G_STRFUNC << ": mime_type is empty.\n";
523
  }
Murray Cumming's avatar
Murray Cumming committed
524

525
  Gtk::AppChooserDialog dialog(mime_type);
526 527 528 529
  if(pApp)
    dialog.set_transient_for(*pApp);

  if(dialog.run() != Gtk::RESPONSE_OK)
530
    return;
Murray Cumming's avatar
Murray Cumming committed
531

532
  auto app_info = dialog.get_app_info();
533 534
  if(!app_info)
  {
535
    std::cerr << G_STRFUNC << ": app_info was null.\n";
536
  }
Murray Cumming's avatar
Murray Cumming committed
537

538 539
  open_with(app_info);
}
540

541 542 543 544 545 546 547 548 549 550 551 552 553
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;
  }
Murray Cumming's avatar
Murray Cumming committed
554

555 556
  if(filepath.empty())
  {
557
    std::cerr << G_STRFUNC << ": filepath is empty.\n";
558
  }
Murray Cumming's avatar
Murray Cumming committed
559

Murray Cumming's avatar
Murray Cumming committed
560
  const auto result = chmod(filepath.c_str(), S_IRUSR);
561 562
  if(result != 0)
  {
563
    std::cerr << G_STRFUNC << ": chmod() failed.\n";
564
  }
Murray Cumming's avatar
Murray Cumming committed
565

566 567 568
  //Setting the attribute via gio gives us this exception:
  //"Setting attribute access::can-write not supported"
  /*
569
  auto file = Gio::File::create_for_uri(uri);
570 571 572 573 574 575 576 577 578 579 580 581

  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;
  }
Murray Cumming's avatar
Murray Cumming committed
582

583 584
  if(!file_info)
  {
585
    std::cerr << G_STRFUNC << ": : file_info is null\n";
586 587
    return;
  }
Murray Cumming's avatar
Murray Cumming committed
588

589 590 591 592
  const bool can_write =
    file_info->get_attribute_boolean(G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
  if(!can_write)
    return;
Murray Cumming's avatar
Murray Cumming committed
593

594
  file_info->set_attribute_boolean(G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, false);
Murray Cumming's avatar
Murray Cumming committed
595

596 597 598 599 600 601 602 603 604 605 606
  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;
  }
  */
}

607
Glib::ustring ImageGlom::save_to_temp_file(bool show_progress)
608
{
609
  Glib::ustring uri = FileUtils::get_temp_file_uri("glom_image");
610
  if(uri.empty())
611
  {
612
    std::cerr << G_STRFUNC << ": : uri is empty.\n";
613
  }
Murray Cumming's avatar
Murray Cumming committed
614

615 616 617 618 619
  bool saved = false;
  if(show_progress)
    saved = save_file(uri);
  else
    saved = save_file_sync(uri);
Murray Cumming's avatar
Murray Cumming committed
620

621 622 623
  if(!saved)
  {
    uri = Glib::ustring();
624
    std::cerr << G_STRFUNC << ": save_file() failed.\n";
625
  }
626 627 628 629 630 631 632
  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);
  }
633 634 635 636 637 638

  return uri;
}

void ImageGlom::open_with(const Glib::RefPtr<Gio::AppInfo>& app_info)
{
Murray Cumming's avatar
Murray Cumming committed
639
  const auto uri = save_to_temp_file();
640
  if(uri.empty())
641 642 643 644
    return;

  if(app_info)
  {
Murray Cumming's avatar
Murray Cumming committed
645
    app_info->launch_uri(uri); //TODO: Get a GdkAppLaunchContext?
646 647 648
  }
  else
  {
Murray Cumming's avatar
Murray Cumming committed
649
    //TODO: Avoid duplication in xsl_utils.cc, by moving this into a utility function:
650 651 652 653
#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.
654 655
    // TODO: and this might not be necessary with Gio::AppInfo::launch_default_for_uri().
    //   Previously we used gtk_show_uri().
656 657
    ShellExecute(0, "open", uri.c_str(), 0, 0, SW_SHOW);
#else
658
    Gio::AppInfo::launch_default_for_uri(uri);
659 660 661
#endif //G_OS_WIN32
  }
}
662

663

664
static void set_file_filter_images(Gtk::FileChooser& file_chooser)
665
{
666
  //Get image formats only:
667
  auto filter = Gtk::FileFilter::create();
668 669
  filter->set_name(_("Images"));
  filter->add_pixbuf_formats();
670
  file_chooser.add_filter(filter);
Murray Cumming's avatar
Murray Cumming committed
671

Murray Cumming's avatar
Murray Cumming committed
672
  ev_document_factory_add_filters(GTK_WIDGET(file_chooser.gobj()), nullptr);
Murray Cumming's avatar
Murray Cumming committed
673

674 675
  //Make Images the currently-selected one:
  file_chooser.set_filter(filter);
Murray Cumming's avatar
Murray Cumming committed
676

677
  /*  ev_document_factory_add_filters() add this already:
678 679 680 681
  filter = Gtk::FileFilter::create();
  filter->set_name(_("All Files"));
  filter->add_pattern("*");
  file_chooser.add_filter(filter);
682
  */
683 684
}

685
void ImageGlom::on_menupopup_activate_save_file()
686
{
Murray Cumming's avatar
Murray Cumming committed
687
  auto pApp = get_appwindow();
688 689 690 691

  Gtk::FileChooserDialog dialog(_("Save Image"), Gtk::FILE_CHOOSER_ACTION_SAVE);
  if(pApp)
    dialog.set_transient_for(*pApp);
Murray Cumming's avatar
Murray Cumming committed
692

693
  set_file_filter_images(dialog);
694

695 696
  dialog.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
  dialog.add_button(_("_Save"), Gtk::RESPONSE_OK);
Murray Cumming's avatar
Murray Cumming committed
697
  const auto response = dialog.run();
698 699 700
  dialog.hide();
  if(response != Gtk::RESPONSE_OK)
    return;
Murray Cumming's avatar
Murray Cumming committed
701

Murray Cumming's avatar
Murray Cumming committed
702
  const auto uri = dialog.get_uri();
703 704
  if(uri.empty())
    return;
Murray Cumming's avatar
Murray Cumming committed
705

706 707 708
  save_file(uri);
}

709 710
bool ImageGlom::save_file_sync(const Glib::ustring& uri)
{
Murray Cumming's avatar
Murray Cumming committed
711
  //TODO: We should still do this asynchronously,
712 713 714
  //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
715

Murray Cumming's avatar
Murray Cumming committed
716
  const auto gda_binary = get_binary();
717 718
  if(!gda_binary)
  {
719
    std::cerr << G_STRFUNC << ": GdaBinary is null\n";
720 721
    return false;
  }
Murray Cumming's avatar
Murray Cumming committed
722

723 724
  if(!gda_binary->data)
  {
725
    std::cerr << G_STRFUNC << ": GdaBinary::data is null\n";
726 727 728 729 730
    return false;
  }

  try
  {
Murray Cumming's avatar
Murray Cumming committed
731
    const auto filepath = Glib::filename_from_uri(uri);
732 733 734 735 736 737 738
    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;
  }
Murray Cumming's avatar
Murray Cumming committed
739

740 741 742
  return true;
}

743 744
bool ImageGlom::save_file(const Glib::ustring& uri)
{
Murray Cumming's avatar
Murray Cumming committed
745
  DialogImageSaveProgress* dialog_save = nullptr;
746 747 748
  Utils::get_glade_widget_derived_with_warning(dialog_save);
  if(!dialog_save)
    return false;
Murray Cumming's avatar
Murray Cumming committed
749

750
  // Automatically delete the dialog when we no longer need it:
751
  std::shared_ptr<Gtk::Dialog> dialog_keeper(dialog_save);
752

Murray Cumming's avatar
Murray Cumming committed
753
  auto pApp = get_appwindow();
754 755 756
  if(pApp)
    dialog_save->set_transient_for(*pApp);

Murray Cumming's avatar
Murray Cumming committed
757
  const auto gda_binary = get_binary();
758 759 760 761
  if(!gda_binary)
    return false;

  dialog_save->set_image_data(*gda_binary);
762 763
  dialog_save->save(uri);

764
  dialog_save->run();
765

766 767 768
  return true;
}

769
void ImageGlom::on_menupopup_activate_select_file()
770 771 772
{
  if(m_read_only)
    return;
Murray Cumming's avatar
Murray Cumming committed
773

Murray Cumming's avatar
Murray Cumming committed
774
  auto pApp = get_appwindow();