/* Glom * * Copyright (C) 2001-2004 Murray Cumming * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "imageglom.h" #include #include #include #include #include #include #include #include // for cout, endl namespace Glom { ImageGlom::type_vec_ustrings ImageGlom::m_evince_supported_mime_types; ImageGlom::ImageGlom() : m_ev_view(0), m_ev_document_model(0), m_pMenuPopup_UserMode(0) { init(); } ImageGlom::ImageGlom(BaseObjectType* cobject, const Glib::RefPtr& /* builder */) : Gtk::EventBox(cobject), m_ev_view(0), m_ev_document_model(0), m_pMenuPopup_UserMode(0) { init(); } void ImageGlom::init() { m_ev_view = EV_VIEW(ev_view_new()); //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); m_read_only = false; #ifndef GLOM_ENABLE_CLIENT_ONLY setup_menu(); #endif // !GLOM_ENABLE_CLIENT_ONLY setup_menu_usermode(); //m_image.set_size_request(150, 150); 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(); add(m_frame); } ImageGlom::~ImageGlom() { } void ImageGlom::set_layout_item(const sharedptr& layout_item, const Glib::ustring& table_name) { LayoutWidgetField::set_layout_item(layout_item, table_name); #ifdef GTKMM_ATKMM_ENABLED get_accessible()->set_name(layout_item->get_name()); #endif } bool ImageGlom::on_button_press_event(GdkEventButton *event) { GdkModifierType mods; gdk_window_get_pointer( gtk_widget_get_window (Gtk::Widget::gobj()), 0, 0, &mods ); //Enable/Disable items. //We did this earlier, but get_application is more likely to work now: Application* pApp = get_application(); if(pApp) { #ifndef GLOM_ENABLE_CLIENT_ONLY 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. #endif // !GLOM_ENABLE_CLIENT_ONLY //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. #ifndef GLOM_ENABLE_CLIENT_ONLY if(pApp->get_userlevel() == AppState::USERLEVEL_DEVELOPER) { if(mods & GDK_BUTTON3_MASK) { //Give user choices of actions on this item: m_pMenuPopup->popup(event->button, event->time); return true; //We handled this event. } } else #endif // !GLOM_ENABLE_CLIENT_ONLY { // We cannot be in developer mode in client only mode. if(mods & GDK_BUTTON3_MASK) { //Give user choices of actions on this item: m_pMenuPopup_UserMode->popup(event->button, event->time); return true; //We handled this event. } } //Single-click to select file: if(mods & GDK_BUTTON1_MASK) { on_menupopup_activate_select_file(); return true; //We handled this event. } } return Gtk::EventBox::on_button_press_event(event); } Application* ImageGlom::get_application() { Gtk::Container* pWindow = get_toplevel(); //TODO: This only works when the child widget is already in its parent. return dynamic_cast(pWindow); } bool ImageGlom::get_has_original_data() const { return true; //TODO. } /* void ImageGlom::set_pixbuf(const Glib::RefPtr& pixbuf) { m_pixbuf_original = pixbuf; show_image_data(); } */ void ImageGlom::set_value(const Gnome::Gda::Value& value) { // Remember original data m_original_data = Gnome::Gda::Value(); m_original_data = value; show_image_data(); } Gnome::Gda::Value ImageGlom::get_value() const { //TODO: Return the data from the file that was just chosen. //Don't store the original here any longer than necessary, if(m_original_data.get_value_type() != G_TYPE_NONE) return m_original_data; if(m_pixbuf_original) { try { gchar* buffer = 0; gsize buffer_size = 0; std::vector list_keys; std::vector list_values; //list_keys.push_back("quality"); //For jpeg only. //list_values.push_back("95"); m_pixbuf_original->save_to_buffer(buffer, buffer_size, GLOM_IMAGE_FORMAT, list_keys, list_values); //Always store images as the standard format in the database. //g_warning("ImageGlom::get_value(): debug: to db: "); //for(int i = 0; i < 10; ++i) // g_warning("%02X (%c), ", (guint8)buffer[i], buffer[i]); GdaBinary* bin = g_new(GdaBinary, 1); bin->data = reinterpret_cast(buffer); bin->binary_length = buffer_size; m_original_data = Gnome::Gda::Value(); m_original_data.Glib::ValueBase::init(GDA_TYPE_BINARY); gda_value_take_binary(m_original_data.gobj(), bin); buffer = 0; return m_original_data; } catch(const Glib::Exception& ex) { std::cerr << G_STRFUNC << ": " << ex.what() << std::endl; } } return Gnome::Gda::Value(); } void ImageGlom::on_size_allocate(Gtk::Allocation& allocation) { Gtk::EventBox::on_size_allocate(allocation); if(m_pixbuf_original) { Glib::RefPtr pixbuf_scaled = get_scaled_image(); m_image.set(pixbuf_scaled); } } static void image_glom_ev_job_finished(EvJob* job, void* user_data) { 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) { 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); ev_view_set_loading(m_ev_view, FALSE); } Glib::ustring ImageGlom::get_mime_type() const { const GdaBinary* gda_binary = gda_value_get_binary(m_original_data.gobj()); 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)) { 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; } } } void ImageGlom::show_image_data() { bool use_evince = false; if(!Conversions::value_is_empty(m_original_data)) { const Glib::ustring mime_type = get_mime_type(); //std::cout << "mime_type=" << mime_type << std::endl; 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()) { use_evince = true; } } m_frame.remove(); //Clear all possible display widgets: m_pixbuf_original.reset(); m_image.set(m_pixbuf_original); 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(); gtk_widget_show(GTK_WIDGET(m_ev_view)); gtk_container_add(GTK_CONTAINER(m_frame.gobj()), GTK_WIDGET(m_ev_view)); 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); ev_document_model_set_continuous(m_ev_document_model, FALSE); //Show only one page. ev_view_set_loading(m_ev_view, TRUE); g_signal_connect (job, "finished", G_CALLBACK (image_glom_ev_job_finished), this); ev_job_scheduler_push_job (job, EV_JOB_PRIORITY_NONE); } else { //Use GtkImage instead: gtk_widget_hide(GTK_WIDGET(m_ev_view)); m_image.show(); m_frame.add(m_image); m_pixbuf_original = Utils::get_pixbuf_for_gda_value(m_original_data); if(m_pixbuf_original) { Glib::RefPtr pixbuf_scaled = get_scaled_image(); m_image.set(pixbuf_scaled); } else m_image.set(Gtk::Stock::MISSING_IMAGE, Gtk::ICON_SIZE_DIALOG); } } Glib::RefPtr ImageGlom::get_scaled_image() { Glib::RefPtr pixbuf = m_pixbuf_original; if(!pixbuf) return pixbuf; const Gtk::Allocation allocation = m_image.get_allocation(); const int pixbuf_height = pixbuf->get_height(); const int pixbuf_width = pixbuf->get_width(); const int allocation_height = allocation.get_height(); const int allocation_width = allocation.get_width(); //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; if( (pixbuf_height > allocation_height) || (pixbuf_width > allocation_width) ) { if(true) //allocation_height > 10 || allocation_width > 10) { Glib::RefPtr pixbuf_scaled = Utils::image_scale_keeping_ratio(pixbuf, allocation_height, allocation_width); //Don't set a new pixbuf if the dimensions have not changed: Glib::RefPtr pixbuf_in_image; 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()) ) { /* 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; } */ return pixbuf_scaled; } else { //std::cout << "scaled image not used." << std::endl; } } } //std::cout << "get_scaled(): returning original" << std::endl; return pixbuf; } void ImageGlom::on_menupopup_activate_open_file() { open_with(); } void ImageGlom::on_menupopup_activate_open_file_with() { Application* pApp = get_application(); //Offer the user a choice of suitable applications: 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); if(pApp) dialog.set_transient_for(*pApp); if(dialog.run() != Gtk::RESPONSE_OK) return; Glib::RefPtr app_info = dialog.get_app_info(); if(!app_info) { std::cerr << G_STRFUNC << ": app_info was null." << std::endl; } open_with(app_info); } Glib::ustring ImageGlom::save_to_temp_file(bool show_progress) { Glib::ustring uri; //Get a temporary file path: std::string filepath; const int filehandle = Glib::file_open_tmp(filepath); ::close(filehandle); if(filepath.empty()) { std::cerr << G_STRFUNC << ": Glib::file_open_tmp() returned an empty filepath" << std::endl; return uri; } uri = Glib::filename_to_uri(filepath); bool saved = false; if(show_progress) saved = save_file(uri); else saved = save_file_sync(uri); if(!saved) { uri = Glib::ustring(); std::cerr << G_STRFUNC << ": save_file() failed." << std::endl; } return uri; } void ImageGlom::open_with(const Glib::RefPtr& app_info) { const Glib::ustring uri = save_to_temp_file(); if(uri.empty()) return; if(app_info) { std::vector vec_uris; vec_uris.push_back(uri); app_info->launch_uris(vec_uris, 0); //TODO: Get a GdkAppLaunchContext? //TODO Use this instead when we can use glibmm 3.2: //app_info->launch_uri(uri); } 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. // TODO: and this might not be necessary with Gio::AppInfo::launch_default_for_uri(). // Previously we used gtk_show_uri(). ShellExecute(0, "open", uri.c_str(), 0, 0, SW_SHOW); #else Gio::AppInfo::launch_default_for_uri(uri); #endif //G_OS_WIN32 } } static void set_file_filter_images(Gtk::FileChooser& file_chooser) { //Get image formats only: Glib::RefPtr filter = Gtk::FileFilter::create(); filter->set_name(_("Images")); filter->add_pixbuf_formats(); file_chooser.add_filter(filter); ev_document_factory_add_filters(GTK_WIDGET(file_chooser.gobj()), 0); /* ev_document_factory_add_filters() add this already: filter = Gtk::FileFilter::create(); filter->set_name(_("All Files")); filter->add_pattern("*"); file_chooser.add_filter(filter); */ } void ImageGlom::on_menupopup_activate_save_file() { Application* pApp = get_application(); Gtk::FileChooserDialog dialog(_("Save Image"), Gtk::FILE_CHOOSER_ACTION_SAVE); if(pApp) dialog.set_transient_for(*pApp); set_file_filter_images(dialog); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); 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); } 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. const GdaBinary* gda_binary = gda_value_get_binary(m_original_data.gobj()); 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; } 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 dialog_keeper(dialog_save); Application* pApp = get_application(); if(pApp) dialog_save->set_transient_for(*pApp); const GdaBinary* gda_binary = gda_value_get_binary(m_original_data.gobj()); if(!gda_binary) return false; dialog_save->set_image_data(*gda_binary); dialog_save->save(uri); dialog_save->run(); return true; } void ImageGlom::on_menupopup_activate_select_file() { if(m_read_only) return; Application* pApp = get_application(); //TODO: Use Hildon::FileChooser for Maemo. Gtk::FileChooserDialog dialog(_("Choose Image"), Gtk::FILE_CHOOSER_ACTION_OPEN); if(pApp) dialog.set_transient_for(*pApp); set_file_filter_images(dialog); dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); dialog.add_button(_("Select"), Gtk::RESPONSE_OK); int response = dialog.run(); dialog.hide(); if((response != Gtk::RESPONSE_CANCEL) && (response != Gtk::RESPONSE_DELETE_EVENT)) { const Glib::ustring uri = dialog.get_uri(); if(!uri.empty()) { DialogImageLoadProgress* dialog; Utils::get_glade_widget_derived_with_warning(dialog); if(dialog) { // Automatically delete the dialog when we no longer need it: std::auto_ptr dialog_keeper(dialog); if(pApp) dialog->set_transient_for(*pApp); dialog->load(uri); if(dialog->run() == Gtk::RESPONSE_ACCEPT) { GdaBinary* bin = g_new(GdaBinary, 1); std::auto_ptr image_data = dialog->get_image_data(); bin->data = image_data->data; bin->binary_length = image_data->binary_length; m_original_data = Gnome::Gda::Value(); m_original_data.Glib::ValueBase::init(GDA_TYPE_BINARY); gda_value_take_binary(m_original_data.gobj(), bin); show_image_data(); signal_edited().emit(); } } } } } 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(); 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) { const GdaBinary* gda_binary = gda_value_get_binary(m_original_data.gobj()); if(!gda_binary) return; if(!gda_binary->data) return; selection_data.set(mime_type, 8, gda_binary->data, gda_binary->binary_length); // This set() override uses an 8-bit text format for the data. //selection_data.set_pixbuf(m_pixbuf_clipboard); } else { std::cout << "ExampleWindow::on_clipboard_get(): Unexpected clipboard target format. expected: " << mime_type << std::endl; } } void ImageGlom::on_clipboard_clear() { if(m_read_only) return; m_pixbuf_clipboard.reset(); } void ImageGlom::on_menupopup_activate_copy() { if(m_pixbuf_original) { //When copy is used, store it here until it is pasted. m_pixbuf_clipboard = m_pixbuf_original->copy(); //TODO: Get it from the DB, when we stop storing the original here instead of just the preview. } else m_pixbuf_clipboard.reset(); Glib::RefPtr refClipboard = Gtk::Clipboard::get(); //Targets: const Glib::ustring mime_type = get_mime_type(); if(mime_type.empty()) { std::cerr << G_STRFUNC << ": mime_type is empty." << std::endl; } std::vector listTargets; listTargets.push_back( Gtk::TargetEntry(mime_type) ); refClipboard->set( listTargets, sigc::mem_fun(*this, &ImageGlom::on_clipboard_get), sigc::mem_fun(*this, &ImageGlom::on_clipboard_clear) ); } void ImageGlom::on_clipboard_received_image(const Glib::RefPtr& pixbuf) { if(m_read_only) return; if(pixbuf) { // Clear original data of previous image m_original_data = Gnome::Gda::Value(); m_pixbuf_original = pixbuf; show_image_data(); signal_edited().emit(); } } void ImageGlom::on_menupopup_activate_paste() { if(m_read_only) return; //Tell the clipboard to call our method when it is ready: Glib::RefPtr refClipboard = Gtk::Clipboard::get(); if(refClipboard) refClipboard->request_image( sigc::mem_fun(*this, &ImageGlom::on_clipboard_received_image) ); } void ImageGlom::on_menupopup_activate_clear() { if(m_read_only) return; m_original_data = Gnome::Gda::Value(); show_image_data(); signal_edited().emit(); } void ImageGlom::setup_menu_usermode() { m_refActionGroup_UserModePopup = Gtk::ActionGroup::create(); m_refActionGroup_UserModePopup->add(Gtk::Action::create("ContextMenu_UserMode", "Context Menu") ); m_refActionOpenFile = Gtk::Action::create("ContextOpenFile", Gtk::Stock::OPEN); m_refActionOpenFileWith = Gtk::Action::create("ContextOpenFileWith", Gtk::Stock::OPEN, _("Open With")); m_refActionSaveFile = Gtk::Action::create("ContextSaveFile", Gtk::Stock::SAVE); m_refActionSelectFile = Gtk::Action::create("ContextSelectFile", Gtk::Stock::EDIT, _("Choose File")); m_refActionCopy = Gtk::Action::create("ContextCopy", Gtk::Stock::COPY); m_refActionPaste = Gtk::Action::create("ContextPaste", Gtk::Stock::PASTE); m_refActionClear = Gtk::Action::create("ContextClear", Gtk::Stock::CLEAR); m_refActionGroup_UserModePopup->add(m_refActionOpenFile, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file) ); m_refActionGroup_UserModePopup->add(m_refActionOpenFileWith, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_open_file_with) ); m_refActionGroup_UserModePopup->add(m_refActionSaveFile, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_save_file) ); m_refActionGroup_UserModePopup->add(m_refActionSelectFile, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_select_file) ); m_refActionGroup_UserModePopup->add(m_refActionCopy, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_copy) ); m_refActionGroup_UserModePopup->add(m_refActionPaste, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_paste) ); m_refActionGroup_UserModePopup->add(m_refActionClear, sigc::mem_fun(*this, &ImageGlom::on_menupopup_activate_clear) ); m_refUIManager_UserModePopup = Gtk::UIManager::create(); m_refUIManager_UserModePopup->insert_action_group(m_refActionGroup_UserModePopup); //TODO: add_accel_group(m_refUIManager_UserModePopup->get_accel_group()); try { Glib::ustring ui_info = "" " " " " " " " " " " " " " " " " " " ""; m_refUIManager_UserModePopup->add_ui_from_string(ui_info); } catch(const Glib::Error& ex) { std::cerr << "building menus failed: " << ex.what(); } //Get the menu: m_pMenuPopup_UserMode = dynamic_cast( m_refUIManager_UserModePopup->get_widget("/ContextMenu_UserMode") ); if(!m_pMenuPopup_UserMode) g_warning("menu not found"); } void ImageGlom::do_choose_image() { on_menupopup_activate_select_file(); } void ImageGlom::set_read_only(bool read_only) { m_read_only = read_only; } } //namespace Glom