/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2008 Banco do Brasil S.A. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 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 Lesser General Public License * along with this program. If not, see . */ #include "private.h" #include #include #include #include #include #include #include static void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); G_DEFINE_TYPE(pw3270ApplicationWindow, pw3270ApplicationWindow, GTK_TYPE_APPLICATION_WINDOW); enum { PROP_NONE, PROP_ACTION_NAMES, }; static void destroy(GtkWidget *widget) { size_t ix; pw3270ApplicationWindow * window = PW3270_APPLICATION_WINDOW(widget); debug("%s(%p)",__FUNCTION__,widget); pw3270_application_window_set_active_terminal(widget,NULL); // Destroy popups for(ix = 0; ix < G_N_ELEMENTS(window->popups); ix++) { if(window->popups[ix]) { gtk_widget_destroy(window->popups[ix]); window->popups[ix] = NULL; } } { g_autoptr(GSettings) settings = pw3270_application_window_settings_new(); g_settings_set_int(settings, "width", window->state.width); g_settings_set_int(settings, "height", window->state.height); g_settings_set_boolean(settings, "is-maximized", window->state.is_maximized); g_settings_set_boolean(settings, "is-fullscreen", window->state.is_fullscreen); } if(window->keypads) { g_list_free(window->keypads); window->keypads = NULL; } GTK_WIDGET_CLASS(pw3270ApplicationWindow_parent_class)->destroy(widget); } static void size_allocate(GtkWidget *widget, GtkAllocation *allocation) { // https://developer.gnome.org/SaveWindowState/ GTK_WIDGET_CLASS(pw3270ApplicationWindow_parent_class)->size_allocate(widget, allocation); pw3270ApplicationWindow * window = PW3270_APPLICATION_WINDOW(widget); if(!(window->state.is_maximized || window->state.is_fullscreen)) { gtk_window_get_size(GTK_WINDOW (widget), &window->state.width, &window->state.height); } } static gboolean window_state_event(GtkWidget *widget, GdkEventWindowState *event) { // https://developer.gnome.org/SaveWindowState/ gboolean res = GDK_EVENT_PROPAGATE; if(GTK_WIDGET_CLASS(pw3270ApplicationWindow_parent_class)->window_state_event != NULL) { res = GTK_WIDGET_CLASS(pw3270ApplicationWindow_parent_class)->window_state_event(widget, event); } { pw3270ApplicationWindow * window = PW3270_APPLICATION_WINDOW(widget); window->state.is_maximized = (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0 ? 0 : 1; window->state.is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) == 0 ? 0 : 1; } return res; } static void constructed(GObject *object) { // https://developer.gnome.org/SaveWindowState/ pw3270ApplicationWindow * window = PW3270_APPLICATION_WINDOW(object); g_autoptr(GSettings) settings = pw3270_application_window_settings_new(); if(settings) { // https://developer.gnome.org/SaveWindowState/ window->state.width = g_settings_get_int (settings, "width"); window->state.height = g_settings_get_int (settings, "height"); window->state.is_maximized = g_settings_get_boolean (settings, "is-maximized") ? 1 : 0; window->state.is_fullscreen = g_settings_get_boolean (settings, "is-fullscreen") ? 1 : 0; } gtk_window_set_default_size(GTK_WINDOW (object), window->state.width, window->state.height); if(window->state.is_maximized) gtk_window_maximize(GTK_WINDOW(object)); if(window->state.is_fullscreen) gtk_window_fullscreen(GTK_WINDOW (object)); G_OBJECT_CLASS (pw3270ApplicationWindow_parent_class)->constructed (object); } static void pw3270ApplicationWindow_class_init(pw3270ApplicationWindowClass *klass) { static const char * icon_search_paths[] = { "icons", #ifdef _WIN32 "share/icons", #endif // _WIN32 }; size_t ix; for(ix = 0; ix < G_N_ELEMENTS(icon_search_paths); ix++) { #if defined(DEBUG) lib3270_autoptr(char) path = g_build_filename(g_get_current_dir(),icon_search_paths[ix],NULL); #elif defined(_WIN32) g_autofree gchar * appdir = g_win32_get_package_installation_directory_of_module(NULL); lib3270_autoptr(char) path = g_build_filename(appdir,icon_search_paths[ix],NULL); #else lib3270_autoptr(char) path = lib3270_build_data_filename(icon_search_paths[ix],NULL); #endif if(g_file_test(path,G_FILE_TEST_IS_DIR)) { g_message("Adding '%s' on icon search path",path); gtk_icon_theme_append_search_path( gtk_icon_theme_get_default(), path ); } else { g_message("Folder '%s' is not valid",path); } } #if defined(DEBUG) || defined(_WIN32) { gchar **paths = NULL; gint n_paths = 0; gtk_icon_theme_get_search_path ( gtk_icon_theme_get_default(), &paths, &n_paths ); g_message("Icon search path:"); gint p; for(p = 0; p < n_paths;p++) { g_message("\t%s",paths[p]); } } #endif // DEBUG { GtkWidgetClass *widget = GTK_WIDGET_CLASS(klass); widget->destroy = destroy; widget->window_state_event = window_state_event; widget->size_allocate = size_allocate; } { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->set_property = set_property; object_class->get_property = get_property; object_class->constructed = constructed; g_object_class_install_property( object_class, PROP_ACTION_NAMES, g_param_spec_string ("action-names", N_("Action Names"), N_("The name of the actions in the header bar"), NULL, G_PARAM_WRITABLE|G_PARAM_READABLE) ); } } void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec G_GNUC_UNUSED(*pspec)) { if(prop_id == PROP_ACTION_NAMES) { pw3270_window_set_header_action_names(GTK_WIDGET(object), g_value_get_string(value)); } } void get_property(GObject *object, guint prop_id, GValue *value, GParamSpec G_GNUC_UNUSED(*pspec)) { if(prop_id == PROP_ACTION_NAMES) { g_value_take_string(value,pw3270_window_get_action_names(GTK_WIDGET(object))); } } static void save_keypad_state(GtkWidget *keypad, GtkWidget *window, gboolean visible) { GtkWidget * terminal = pw3270_application_window_get_active_terminal(window); if(!terminal) return; GKeyFile * keyfile = v3270_key_file_get(terminal); if(!keyfile) return; g_key_file_set_boolean( keyfile, "keypads", gtk_widget_get_name(keypad), visible ); v3270_emit_save_settings(terminal,NULL); } static void keypad_hide(GtkWidget *keypad, GtkWidget *window) { save_keypad_state(keypad,window,FALSE); } static void keypad_show(GtkWidget *keypad, GtkWidget *window) { save_keypad_state(keypad,window,TRUE); } static GtkWidget * setup_keypad(pw3270ApplicationWindow *window, GObject * model) { GtkWidget * widget = pw3270_keypad_get_from_model(model); if(!widget) { return NULL; } window->keypads = g_list_prepend(window->keypads,widget); const gchar * name = pw3270_keypad_model_get_name(model); gtk_widget_set_name(widget,name); g_signal_connect(widget,"hide",G_CALLBACK(keypad_hide),window); g_signal_connect(widget,"show",G_CALLBACK(keypad_show),window); g_autofree gchar * action_name = g_strconcat("keypad.",name,NULL); GPropertyAction * action = g_property_action_new( action_name, widget, "visible" ); g_action_map_add_action( G_ACTION_MAP(window), G_ACTION(action) ); return widget; } static void pw3270ApplicationWindow_init(pw3270ApplicationWindow *widget) { // Get settings g_autoptr(GSettings) settings = pw3270_application_window_settings_new(); // Override defaults { // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtksettings.c GtkSettings *settings = gtk_widget_get_settings(GTK_WIDGET (widget)); g_object_set(settings,"gtk-menu-bar-accel","",NULL); } // Setup defaults widget->state.width = 800; widget->state.height = 500; widget->state.is_maximized = 0; widget->state.is_fullscreen = 0; // Create contents GtkBox * container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL,0)); widget->notebook = GTK_NOTEBOOK(gtk_notebook_new()); gtk_notebook_set_scrollable(widget->notebook,TRUE); gtk_notebook_set_show_tabs(widget->notebook,FALSE); gtk_notebook_set_show_border(widget->notebook, FALSE); gtk_notebook_set_group_name(widget->notebook,PACKAGE_NAME ":Terminals"); // Create boxes GtkBox * hBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0)); GtkBox * vBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL,0)); gtk_widget_show(GTK_WIDGET(hBox)); gtk_widget_show(GTK_WIDGET(vBox)); // Create toolbar { widget->toolbar = GTK_TOOLBAR(pw3270_toolbar_new()); g_action_map_add_action( G_ACTION_MAP(widget), G_ACTION(g_property_action_new("toolbar", widget->toolbar, "visible")) ); switch(g_settings_get_int(settings,"toolbar-position")) { case 1: gtk_orientable_set_orientation(GTK_ORIENTABLE(widget->toolbar),GTK_ORIENTATION_HORIZONTAL); gtk_box_pack_end(container,GTK_WIDGET(widget->toolbar),FALSE,TRUE,0); break; case 2: gtk_orientable_set_orientation(GTK_ORIENTABLE(widget->toolbar),GTK_ORIENTATION_VERTICAL); gtk_box_pack_end(hBox,GTK_WIDGET(widget->toolbar),FALSE,TRUE,0); break; case 3: gtk_orientable_set_orientation(GTK_ORIENTABLE(widget->toolbar),GTK_ORIENTATION_VERTICAL); gtk_box_pack_start(hBox,GTK_WIDGET(widget->toolbar),FALSE,TRUE,0); break; default: gtk_orientable_set_orientation(GTK_ORIENTABLE(widget->toolbar),GTK_ORIENTATION_HORIZONTAL); gtk_box_pack_start(container,GTK_WIDGET(widget->toolbar),FALSE,TRUE,0); break; } g_settings_bind( settings, "toolbar-visible", widget->toolbar, "visible", G_SETTINGS_BIND_DEFAULT ); g_settings_bind( settings, "toolbar-icon-type", widget->toolbar, "icon-type", G_SETTINGS_BIND_DEFAULT ); g_settings_bind( settings, "toolbar-style", widget->toolbar, "style", G_SETTINGS_BIND_DEFAULT ); g_settings_bind( settings, "toolbar-icon-size", widget->toolbar, "icon-size", G_SETTINGS_BIND_DEFAULT ); } gtk_box_pack_start(container,GTK_WIDGET(hBox),TRUE,TRUE,0); // // Create and pack keypads? // { GList * keypads = pw3270_application_get_keypad_models(g_application_get_default()); GList * keypad; // Add top Keypads for(keypad = keypads; keypad; keypad = g_list_next(keypad)) { if(pw3270_keypad_get_position(G_OBJECT(keypad->data)) == KEYPAD_POSITION_TOP) { gtk_box_pack_start( vBox, setup_keypad(widget, G_OBJECT(keypad->data)), FALSE,FALSE,0 ); } } // Add left keypads for(keypad = keypads; keypad; keypad = g_list_next(keypad)) { if(pw3270_keypad_get_position(G_OBJECT(keypad->data)) == KEYPAD_POSITION_LEFT) { gtk_box_pack_start( hBox, setup_keypad(widget, G_OBJECT(keypad->data)), FALSE,FALSE,0 ); } } // Add center notebook gtk_box_pack_start(vBox,GTK_WIDGET(widget->notebook),TRUE,TRUE,0); gtk_box_pack_start(hBox,GTK_WIDGET(vBox),TRUE,TRUE,0); // Add bottom keypads for(keypad = keypads; keypad; keypad = g_list_next(keypad)) { if(pw3270_keypad_get_position(G_OBJECT(keypad->data)) == KEYPAD_POSITION_BOTTOM) { gtk_box_pack_end( vBox, setup_keypad(widget, G_OBJECT(keypad->data)), FALSE,FALSE,0 ); } } // Add right keypads for(keypad = keypads; keypad; keypad = g_list_next(keypad)) { if(pw3270_keypad_get_position(G_OBJECT(keypad->data)) == KEYPAD_POSITION_RIGHT) { gtk_box_pack_end( hBox, setup_keypad(widget, G_OBJECT(keypad->data)), FALSE,FALSE,0 ); } } } gtk_widget_show_all(GTK_WIDGET(widget->notebook)); gtk_widget_show(GTK_WIDGET(container)); gtk_container_add(GTK_CONTAINER(widget),GTK_WIDGET(container)); // // Setup tn3270 actions. // g_action_map_add_v3270_actions(G_ACTION_MAP(widget)); g_action_map_add_lib3270_actions(G_ACTION_MAP(widget)); g_action_map_add_lib3270_toggles(G_ACTION_MAP(widget)); // Map special actions { size_t ix; GAction * actions[] = { pw3270_action_session_properties_new(), pw3270_action_save_session_preferences_new(), pw3270_file_transfer_action_new(), pw3270_action_window_close_new(), pw3270_action_connect_new(), v3270_pfkey_action_new(), v3270_pakey_action_new(), #ifndef __APPLE__ pw3270_action_save_session_shortcut_new(), #endif // __APPLE__ }; for(ix = 0; ix < G_N_ELEMENTS(actions); ix++) { if(!g_action_get_name(actions[ix])) { g_warning("Window special action %u is unnamed",(unsigned int) ix); } else { g_action_map_add_action(G_ACTION_MAP(widget),actions[ix]); } } } // // Bind properties // #ifdef __APPLE__ gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(widget),TRUE); #else g_action_map_add_action( G_ACTION_MAP(widget), G_ACTION(g_property_action_new("menubar", widget, "show-menubar")) ); #endif // !__APPLE__ g_settings_bind( settings, "toolbar-action-names", widget->toolbar, "action-names", G_SETTINGS_BIND_DEFAULT ); } static void page_added(GtkNotebook *notebook, GtkWidget *child, guint G_GNUC_UNUSED(page_num), GtkApplication * application) { debug("%s(%p)",__FUNCTION__,child); gtk_notebook_set_show_tabs(notebook,gtk_notebook_get_n_pages(notebook) > 1); // Call plugins int (*call)(GtkWidget *); GSList * item; for(item = pw3270_application_get_plugins(G_APPLICATION(application)); item; item = g_slist_next(item)) { if(g_module_symbol((GModule *) item->data, "pw3270_plugin_page_added", (gpointer *) &call)) { call(child); } } } static void page_removed(GtkNotebook *notebook, GtkWidget *child, guint G_GNUC_UNUSED(page_num), GtkApplication * application) { debug("%s(%p)",__FUNCTION__,child); gtk_notebook_set_show_tabs(notebook,gtk_notebook_get_n_pages(notebook) > 1); // Call plugins int (*call)(GtkWidget *); GSList * item; for(item = pw3270_application_get_plugins(G_APPLICATION(application)); item; item = g_slist_next(item)) { if(g_module_symbol((GModule *) item->data, "pw3270_plugin_page_removed", (gpointer *) &call)) { call(child); } } } GtkWidget * pw3270_application_window_new(GtkApplication * application, const gchar *session_file) { gchar *title = _( "IBM 3270 Terminal emulator" ); g_return_val_if_fail(PW3270_IS_APPLICATION(application),NULL); size_t ix; g_return_val_if_fail(GTK_IS_APPLICATION(application), NULL); pw3270ApplicationWindow * window = g_object_new( PW3270_TYPE_APPLICATION_WINDOW, "application", application, NULL); g_signal_connect(G_OBJECT(window->notebook), "page-added", G_CALLBACK(page_added), application); g_signal_connect(G_OBJECT(window->notebook), "page-removed", G_CALLBACK(page_removed), application); // // Get builder // g_autoptr(GtkBuilder) builder = pw3270_application_builder_new(G_APPLICATION(application)); // Load popup menus. const gchar * popup_menus[PW3270_APP_WINDOW_POPUP_COUNT] = { "popup-over-selected-area", // PW3270_APP_WINDOW_POPUP_OVER_SELECTED_AREA "popup-over-unselected-area", // PW3270_APP_WINDOW_POPUP_OVER_UNSELECTED_AREA "popup-over-oia", // PW3270_APP_WINDOW_POPUP_OVER_OIA "popup-when-offline" // PW3270_APP_WINDOW_POPUP_WHEN_OFFLINE }; for(ix = 0; ix < G_N_ELEMENTS(popup_menus); ix++) { GObject * model = gtk_builder_get_object(builder, popup_menus[ix]); if(model) { window->popups[ix] = gtk_menu_new_from_model(G_MENU_MODEL(model)); gtk_menu_attach_to_widget(GTK_MENU(window->popups[ix]),GTK_WIDGET(window),NULL); } } // Setup and show main window { g_autoptr(GSettings) settings = pw3270_application_window_settings_new(); #ifdef DEBUG PW3270_UI_STYLE style = PW3270_UI_STYLE_AUTOMATIC; #else PW3270_UI_STYLE style = pw3270_application_get_ui_style(G_APPLICATION(application)); #endif // DEBUG if(style == PW3270_UI_STYLE_AUTOMATIC) { #if defined(__APPLE__) style = PW3270_UI_STYLE_GNOME; g_settings_set_int(settings,"header-icon-type",1); #elif defined( G_OS_UNIX ) style = PW3270_UI_STYLE_GNOME; g_settings_set_boolean(settings,"menubar-visible",FALSE); g_settings_set_int(settings,"header-icon-type",1); #elif defined( G_OS_WIN32 ) style = PW3270_UI_STYLE_CLASSICAL; g_settings_set_boolean(settings,"menubar-visible",TRUE); g_settings_set_int(settings,"header-icon-type",0); #else #error Unsupported platform #endif // G_OS_UNIX g_settings_set_boolean(settings,"toolbar-visible",TRUE); pw3270_application_set_ui_style(G_APPLICATION(application),style); } if(style == PW3270_UI_STYLE_GNOME) { // Create header bar GtkHeaderBar * header = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_window_set_titlebar(GTK_WINDOW(window), GTK_WIDGET(header)); gtk_header_bar_set_show_close_button(header,TRUE); gtk_header_bar_set_title(header,title); g_settings_bind( settings, "has-subtitle", header, "has-subtitle", G_SETTINGS_BIND_DEFAULT ); // Show the new header gtk_widget_show_all(GTK_WIDGET(header)); g_settings_bind( settings, "header-action-names", window, "action-names", G_SETTINGS_BIND_DEFAULT ); } else { gtk_window_set_title(GTK_WINDOW(window), title); } #ifndef __APPLE__ g_settings_bind( settings, "menubar-visible", window, "show-menubar", G_SETTINGS_BIND_DEFAULT ); #endif // !__APPLE__ } // Setup default position and size gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); // Create terminal widget GtkWidget * terminal = pw3270_application_window_new_tab(GTK_WIDGET(window), session_file); // Create property actions static const struct Property { LIB3270_ACTION_GROUP group; const gchar *name; } properties[] = { { .name = "model-number", .group = LIB3270_ACTION_GROUP_OFFLINE }, { .name = "font-family", .group = LIB3270_ACTION_GROUP_NONE }, { .name = "dynamic-font-spacing", .group = LIB3270_ACTION_GROUP_NONE }, { .name = "trace", .group = LIB3270_ACTION_GROUP_NONE }, }; for(ix = 0; ix < G_N_ELEMENTS(properties); ix++) { GAction * action = G_ACTION(v3270_property_action_new(terminal,properties[ix].name,properties[ix].group)); if(!g_action_get_name(action)) { g_warning("Window property action %s is unnamed",properties[ix].name); } else { g_action_map_add_action(G_ACTION_MAP(window),action); } } pw3270_application_window_set_active_terminal(GTK_WIDGET(window),terminal); return GTK_WIDGET(window); } void pw3270_window_set_current_page(GtkWidget *window, gint page_num) { g_return_if_fail(PW3270_IS_APPLICATION_WINDOW(window)); GtkNotebook * notebook = PW3270_APPLICATION_WINDOW(window)->notebook; debug("Selecting tab %d", page_num); gtk_notebook_set_current_page(notebook, page_num); gtk_widget_grab_focus(gtk_notebook_get_nth_page(notebook, page_num)); } void pw3270_window_set_subtitle(GtkWidget *window, const gchar *subtitle) { g_return_if_fail(PW3270_IS_APPLICATION_WINDOW(window)); GtkWidget * title_bar = gtk_window_get_titlebar(GTK_WINDOW(window)); if(title_bar && GTK_IS_HEADER_BAR(title_bar) && gtk_header_bar_get_has_subtitle(GTK_HEADER_BAR(title_bar))) { gtk_header_bar_set_subtitle(GTK_HEADER_BAR(title_bar), subtitle); } } GtkWidget * pw3270_application_window_get_active_terminal(GtkWidget *widget) { return PW3270_APPLICATION_WINDOW(widget)->terminal; } void pw3270_application_window_set_active_terminal(GtkWidget *widget, GtkWidget *terminal) { size_t ix; pw3270ApplicationWindow * window = PW3270_APPLICATION_WINDOW(widget); if(window->terminal == terminal) return; if(terminal && GTK_IS_V3270(terminal)) { window->terminal = terminal; // Store the active terminal widget. gtk_widget_grab_default(terminal); debug("Terminal %p is now default",terminal); // Change window title g_autofree gchar * title = v3270_get_session_title(terminal); gtk_window_set_title(GTK_WINDOW(window), title); pw3270_window_set_subtitle(GTK_WIDGET(window), v3270_is_connected(terminal) ? _("Connected to host") : _("Disconnected from host")); // Setup keypads if(window->keypads) { GKeyFile * keyfile = v3270_key_file_get(terminal); if(keyfile) { GList * keypad; for(keypad = window->keypads; keypad; keypad = g_list_next(keypad)) { GtkWidget *kWidget = GTK_WIDGET(keypad->data); if(g_key_file_get_boolean(keyfile,"keypads",gtk_widget_get_name(kWidget),NULL)) { gtk_widget_show(kWidget); } else { gtk_widget_hide(kWidget); } } } } } else { terminal = NULL; pw3270_window_set_subtitle(GTK_WIDGET(window), _("Disconnected from host")); } // Update window actions { gchar ** actions = g_action_group_list_actions(G_ACTION_GROUP(window)); for(ix = 0; actions[ix]; ix++) { GAction * action = g_action_map_lookup_action(G_ACTION_MAP(window), actions[ix]); if(action && V3270_IS_ACTION(action)) { v3270_action_set_terminal_widget(action,terminal); } } g_strfreev(actions); } // Update application actions { GtkApplication * application = GTK_APPLICATION(gtk_window_get_application(GTK_WINDOW(window))); if(application) { gchar ** actions = g_action_group_list_actions(G_ACTION_GROUP(application)); for(ix = 0; actions[ix]; ix++) { GAction * action = g_action_map_lookup_action(G_ACTION_MAP(application), actions[ix]); if(action && V3270_IS_ACTION(action)) { v3270_action_set_terminal_widget(action,terminal); } } } } } GList * pw3270_application_window_get_keypads(GtkWidget *window) { g_return_val_if_fail(PW3270_IS_APPLICATION_WINDOW(window),NULL); return PW3270_APPLICATION_WINDOW(window)->keypads; }