/** * @file viewer-widget.cc * @brief Part of GNOME Commander - A GNOME based file manager * * @copyright (C) 2006 Assaf Gordon\n * @copyright (C) 2007-2012 Piotr Eljasiak\n * @copyright (C) 2013-2023 Uwe Scholz\n * * @copyright 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. * * @copyright 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. * * @copyright 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #include #include #include "libgviewer.h" #include "gnome-cmd-includes.h" #include "gnome-cmd-file.h" #include "utils.h" using namespace std; #define DEFAULT_TAB_SIZE 8 #define DEFAULT_WRAP_MODE TRUE #define DEFAULT_FIXED_LIMIT 80 #define DEFAULT_ENCODING "ASCII" #define DEFAULT_BEST_FIT TRUE #define DEFAULT_SCALE_FACTOR 1.0 /* Class Private Data */ struct GViewerPrivate { GtkWidget *tscrollbox; TextRender *textr; GtkWidget *iscrollbox; ImageRender *imgr; gboolean img_initialized; GtkWidget *last_client; gchar *filename; VIEWERDISPLAYMODE dispmode; }; static GtkTableClass *parent_class = NULL; enum { STATUS_LINE_CHANGED, LAST_SIGNAL }; static guint gviewer_signals[LAST_SIGNAL] = { 0 }; /* Gtk class related static functions */ static void gviewer_init (GViewer *w); static void gviewer_class_init (GViewerClass *klass); static void gviewer_destroy (GtkObject *object); static void gviewer_text_status_update(TextRender *obj, TextRender::Status *status, GViewer *viewer); static void gviewer_image_status_update(ImageRender *obj, ImageRender::Status *status, GViewer *viewer); static gboolean on_text_viewer_button_pressed (GtkWidget *treeview, GdkEventButton *event, GViewer *viewer); static VIEWERDISPLAYMODE guess_display_mode(const char *filename, int len); static void gviewer_auto_detect_display_mode(GViewer *obj); /***************************************** public functions (defined in the header file) *****************************************/ GtkType gviewer_get_type () { static GtkType type = 0; if (type == 0) { GTypeInfo info = { sizeof (GViewerClass), NULL, NULL, (GClassInitFunc) gviewer_class_init, NULL, NULL, sizeof(GViewer), 0, (GInstanceInitFunc) gviewer_init }; type = g_type_register_static (GTK_TYPE_TABLE, "gviewerwidget", &info, (GTypeFlags) 0); } return type; } GtkWidget *gviewer_new () { auto w = static_cast (g_object_new (gviewer_get_type (), NULL)); return GTK_WIDGET (w); } static void gviewer_class_init (GViewerClass *klass) { GtkObjectClass *object_class = GTK_OBJECT_CLASS(klass); parent_class = (GtkTableClass *) gtk_type_class (gtk_table_get_type ()); object_class->destroy = gviewer_destroy; gviewer_signals[STATUS_LINE_CHANGED] = gtk_signal_new ("status-line-changed", GTK_RUN_LAST, G_OBJECT_CLASS_TYPE (object_class), GTK_SIGNAL_OFFSET (GViewerClass, status_line_changed), gtk_marshal_NONE__STRING, GTK_TYPE_NONE, 1, GTK_TYPE_STRING); } static void gviewer_init (GViewer *w) { w->priv = g_new0 (GViewerPrivate, 1); gtk_table_resize (GTK_TABLE (w), 1, 1); gtk_table_set_homogeneous(GTK_TABLE (w), FALSE); w->priv->img_initialized = FALSE; w->priv->dispmode = DISP_MODE_TEXT_FIXED; w->priv->textr = reinterpret_cast (text_render_new()); gviewer_set_tab_size(w, DEFAULT_TAB_SIZE); gviewer_set_wrap_mode(w, DEFAULT_WRAP_MODE); gviewer_set_fixed_limit(w, DEFAULT_FIXED_LIMIT); gviewer_set_encoding(w, DEFAULT_ENCODING); w->priv->tscrollbox = scroll_box_new(); text_render_set_v_adjustment(w->priv->textr, scroll_box_get_v_adjustment(SCROLL_BOX(w->priv->tscrollbox))); text_render_set_h_adjustment(w->priv->textr, scroll_box_get_h_adjustment(SCROLL_BOX(w->priv->tscrollbox))); text_render_attach_external_v_range(w->priv->textr, scroll_box_get_v_range(SCROLL_BOX(w->priv->tscrollbox))); scroll_box_set_client (SCROLL_BOX(w->priv->tscrollbox), GTK_WIDGET (w->priv->textr)); gtk_widget_show (GTK_WIDGET (w->priv->textr)); gtk_widget_show (w->priv->tscrollbox); g_object_ref (w->priv->tscrollbox); w->priv->imgr = reinterpret_cast (image_render_new()); gviewer_set_best_fit(w, DEFAULT_BEST_FIT); gviewer_set_scale_factor(w, DEFAULT_SCALE_FACTOR); w->priv->iscrollbox = scroll_box_new(); image_render_set_v_adjustment (w->priv->imgr, scroll_box_get_v_adjustment (SCROLL_BOX (w->priv->iscrollbox))); image_render_set_h_adjustment (w->priv->imgr, scroll_box_get_h_adjustment (SCROLL_BOX (w->priv->iscrollbox))); image_render_set_best_fit (w->priv->imgr, TRUE); image_render_set_scale_factor (w->priv->imgr, 1); scroll_box_set_client (SCROLL_BOX(w->priv->iscrollbox), GTK_WIDGET (w->priv->imgr)); gtk_widget_show (GTK_WIDGET (w->priv->imgr)); gtk_widget_show (w->priv->iscrollbox); g_object_ref (w->priv->iscrollbox); w->priv->last_client = w->priv->tscrollbox; gtk_table_attach (GTK_TABLE (w), GTK_WIDGET (w->priv->tscrollbox), 0, 1, 0, 1, (GtkAttachOptions)(GTK_FILL|GTK_EXPAND), (GtkAttachOptions)(GTK_FILL|GTK_EXPAND), 0, 0); g_signal_connect (w, "destroy-event", G_CALLBACK (gviewer_destroy), w); g_signal_connect (w->priv->textr, "text-status-changed", G_CALLBACK (gviewer_text_status_update), w); g_signal_connect (w->priv->imgr, "image-status-changed", G_CALLBACK (gviewer_image_status_update), w); g_signal_connect (w->priv->textr, "button-press-event", G_CALLBACK (on_text_viewer_button_pressed), w); } #define MAX_STATUS_LENGTH 128 static void gviewer_text_status_update(TextRender *obj, TextRender::Status *status, GViewer *viewer) { g_return_if_fail (IS_GVIEWER (viewer)); g_return_if_fail (status!=NULL); static gchar temp[MAX_STATUS_LENGTH]; g_snprintf(temp, sizeof (temp), _("Position: %lu of %lu\tColumn: %d\t%s"), (unsigned long) status->current_offset, (unsigned long) status->size, status->column, status->wrap_mode?_("Wrap"):""); gtk_signal_emit (GTK_OBJECT (viewer), gviewer_signals[STATUS_LINE_CHANGED], temp); } static void gviewer_image_status_update(ImageRender *obj, ImageRender::Status *status, GViewer *viewer) { g_return_if_fail (IS_GVIEWER (viewer)); g_return_if_fail (status!=NULL); static gchar temp[MAX_STATUS_LENGTH]; *temp = 0; if (status->image_width > 0 && status->image_height > 0) { gchar zoom[10]; char *size_string = strdup(""); if (!status->best_fit) g_snprintf(zoom, sizeof(zoom), "%i%%", (int)(status->scale_factor*100.0)); g_snprintf (temp, sizeof(temp), "%i x %i %s %i %s %s %s", status->image_width, status->image_height, ngettext ("pixel", "pixels", status->image_height), status->bits_per_sample, ngettext ("bit/sample", "bits/sample", status->bits_per_sample), size_string, status->best_fit?_("(fit to window)"):zoom); free(size_string); } gtk_signal_emit (GTK_OBJECT (viewer), gviewer_signals[STATUS_LINE_CHANGED], temp); } static gboolean on_text_viewer_button_pressed (GtkWidget *treeview, GdkEventButton *event, GViewer *viewer) { if (event->type==GDK_BUTTON_PRESS && event->button==3) { GtkWidget *menu = gtk_menu_new (); GtkWidget *menuitem; menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Copy selection")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU)); g_signal_connect (menuitem, "activate", G_CALLBACK (gviewer_copy_selection), viewer); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); gtk_widget_show_all (menu); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; } return FALSE; } static void gviewer_destroy (GtkObject *widget) { g_return_if_fail (IS_GVIEWER (widget)); GViewer *w = GVIEWER (widget); if (w->priv) { g_object_unref (w->priv->iscrollbox); g_object_unref (w->priv->tscrollbox); g_free (w->priv); w->priv = NULL; } if (GTK_OBJECT_CLASS(parent_class)->destroy) { (*GTK_OBJECT_CLASS(parent_class)->destroy) (widget); } } static VIEWERDISPLAYMODE guess_display_mode(const char *filename, int len) { auto returnValue = DISP_MODE_TEXT_FIXED; auto gFile = g_file_new_for_path(filename); GError *error; error = nullptr; auto gcmdFileInfo = g_file_query_info(gFile, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr, &error); if (error) { g_message ("guess_display_mode: retrieving file info for %s failed: %s", g_file_peek_path(gFile), error->message); g_error_free (error); } auto gFileContentType = g_file_info_get_attribute_string (gcmdFileInfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); if (g_ascii_strncasecmp (gFileContentType, "text/", 5) == 0) returnValue = DISP_MODE_TEXT_FIXED; if (g_ascii_strncasecmp (gFileContentType, "image/", 6) == 0) returnValue = DISP_MODE_IMAGE; if (g_ascii_strncasecmp (gFileContentType, "application/", 12) == 0) returnValue = DISP_MODE_BINARY; g_object_unref(gcmdFileInfo); g_object_unref(gFile); return returnValue; } void gviewer_auto_detect_display_mode(GViewer *obj) { g_return_if_fail (obj != nullptr); const unsigned DETECTION_BUF_LEN = 100; obj->priv->dispmode = DISP_MODE_TEXT_FIXED; if (!obj->priv->textr) return; ViewerFileOps *fops = text_render_get_file_ops(obj->priv->textr); if (!fops) return; int count = MIN(DETECTION_BUF_LEN, gv_file_get_max_offset(fops)); obj->priv->dispmode = guess_display_mode(fops->filename, count); } void gviewer_set_display_mode(GViewer *obj, VIEWERDISPLAYMODE mode) { g_return_if_fail (IS_GVIEWER (obj)); if (mode==DISP_MODE_IMAGE && !obj->priv->img_initialized) { // do lazy-initialization of the image render, only when the user first asks to display the file as image obj->priv->img_initialized = TRUE; image_render_load_file(obj->priv->imgr, obj->priv->filename); } GtkWidget *client = NULL; obj->priv->dispmode = mode; switch (mode) { case DISP_MODE_TEXT_FIXED: client = obj->priv->tscrollbox; text_render_set_display_mode (obj->priv->textr, TextRender::DISPLAYMODE_TEXT); break; case DISP_MODE_BINARY: client = obj->priv->tscrollbox; text_render_set_display_mode (obj->priv->textr, TextRender::DISPLAYMODE_BINARY); break; case DISP_MODE_HEXDUMP: client = obj->priv->tscrollbox; text_render_set_display_mode (obj->priv->textr, TextRender::DISPLAYMODE_HEXDUMP); break; case DISP_MODE_IMAGE: client = obj->priv->iscrollbox; break; default: break; } if (client != obj->priv->last_client) { if (obj->priv->last_client) gtk_container_remove (GTK_CONTAINER(obj), obj->priv->last_client); gtk_widget_grab_focus (GTK_WIDGET (client)); gtk_table_attach (GTK_TABLE (obj), client , 0, 1, 0, 1, (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); switch (mode) { case DISP_MODE_TEXT_FIXED: case DISP_MODE_BINARY: case DISP_MODE_HEXDUMP: text_render_notify_status_changed(obj->priv->textr); break; case DISP_MODE_IMAGE: image_render_notify_status_changed(obj->priv->imgr); break; default: break; } gtk_widget_show (client); obj->priv->last_client = client; } } VIEWERDISPLAYMODE gviewer_get_display_mode(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), DISP_MODE_TEXT_FIXED); return obj->priv->dispmode; } void gviewer_load_file(GViewer *obj, const gchar*filename) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (filename); g_free (obj->priv->filename); obj->priv->filename = g_strdup (filename); text_render_load_file(obj->priv->textr, obj->priv->filename); gviewer_auto_detect_display_mode(obj); gviewer_set_display_mode(obj, obj->priv->dispmode); } void gviewer_set_tab_size(GViewer *obj, int tab_size) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_tab_size(obj->priv->textr, tab_size); } int gviewer_get_tab_size(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), 0); g_return_val_if_fail (obj->priv->textr, 0); return text_render_get_tab_size(obj->priv->textr); } void gviewer_set_wrap_mode(GViewer *obj, gboolean ACTIVE) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_wrap_mode(obj->priv->textr, ACTIVE); } gboolean gviewer_get_wrap_mode(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), FALSE); g_return_val_if_fail (obj->priv->textr, FALSE); return text_render_get_wrap_mode(obj->priv->textr); } void gviewer_set_fixed_limit(GViewer *obj, int fixed_limit) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_fixed_limit(obj->priv->textr, fixed_limit); } int gviewer_get_fixed_limit(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), 0); g_return_val_if_fail (obj->priv->textr, 0); return text_render_get_fixed_limit(obj->priv->textr); } void gviewer_set_encoding(GViewer *obj, const char *encoding) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_encoding(obj->priv->textr, encoding); } const gchar *gviewer_get_encoding(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), NULL); g_return_val_if_fail (obj->priv->textr, NULL); return text_render_get_encoding(obj->priv->textr); } void gviewer_set_font_size(GViewer *obj, int font_size) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_font_size(obj->priv->textr, font_size); } int gviewer_get_font_size(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), 0); g_return_val_if_fail (obj->priv->textr, 0); return text_render_get_font_size(obj->priv->textr); } void gviewer_set_hex_offset_display(GViewer *obj, gboolean HEX_OFFSET) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); text_render_set_hex_offset_display(obj->priv->textr, HEX_OFFSET); } gboolean gviewer_get_hex_offset_display(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), FALSE); g_return_val_if_fail (obj->priv->textr, FALSE); return text_render_get_hex_offset_display(obj->priv->textr); } void gviewer_set_best_fit(GViewer *obj, gboolean active) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->imgr); image_render_set_best_fit(obj->priv->imgr, active); } gboolean gviewer_get_best_fit(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), FALSE); g_return_val_if_fail (obj->priv->textr, FALSE); return image_render_get_best_fit(obj->priv->imgr); } void gviewer_set_scale_factor(GViewer *obj, double scalefactor) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->imgr); image_render_set_scale_factor(obj->priv->imgr, scalefactor); } double gviewer_get_scale_factor(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), 0); g_return_val_if_fail (obj->priv->imgr, 0); return image_render_get_scale_factor(obj->priv->imgr); } TextRender *gviewer_get_text_render(GViewer *obj) { g_return_val_if_fail (IS_GVIEWER (obj), 0); g_return_val_if_fail (obj->priv->textr, 0); return obj->priv->textr; } void gviewer_image_operation(GViewer *obj, ImageRender::DISPLAYMODE op) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->imgr); image_render_operation(obj->priv->imgr, op); } void gviewer_copy_selection(GtkMenuItem *item, GViewer *obj) { g_return_if_fail (IS_GVIEWER (obj)); g_return_if_fail (obj->priv->textr); if (obj->priv->dispmode!=DISP_MODE_IMAGE) text_render_copy_selection(obj->priv->textr); }