/* Copyright 1997 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /***************************************************/ /* File : SaveFile.c */ /* */ /* Purpose: Handle the Save File dialogue (actual */ /* file saving is done in Save.c). Relies */ /* on there being only one Save File */ /* dialogue open at a time (it is a */ /* shared object). */ /* */ /* Author : A.D.Hodgkinson */ /* */ /* History: 03-Sep-97: Created. */ /***************************************************/ #include <stdlib.h> #include <string.h> #include "swis.h" #include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */ #include "wimp.h" #include "wimplib.h" #include "event.h" #include "toolbox.h" #include "window.h" #include "gadgets.h" #include "svcprint.h" #include "Global.h" #include "Utils.h" #include "Browser.h" #include "Fetch.h" /* (For ISLINK macro) */ #include "Filetypes.h" #include "Hotlist.h" #include "Images.h" #include "Menus.h" #include "Object.h" #include "Protocols.h" #include "Save.h" #include "Toolbars.h" #include "URLutils.h" #include "Windows.h" #include "SaveFile.h" /* Local statics */ static browser_data * savefile_browser = NULL; static HStream * savefile_token = NULL; static int savefile_type = 0x000; static ObjectId window_id = 0; static ComponentId parent_component = -1; static ObjectId ancestor_id = 0; /* Static function prototypes */ static int savefile_drag_ended (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle); static int savefile_ok (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle); static int savefile_cancel (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle); /*************************************************/ /* savefile_open_for() */ /* */ /* Creates and opens a Save File dialogue for a */ /* given browser, opening near the pointer. */ /* */ /* Parameters: Pointer to a browser_data struct */ /* which is the ancestor of the */ /* dialogue; */ /* */ /* Object ID to use as a parent, or */ /* 0 for none. */ /*************************************************/ _kernel_oserror * savefile_open_for(browser_data * b, ObjectId parent) { _kernel_oserror * e; ObjectId id; /* Create the dialogue - as this is shared object it */ /* will not be recreated if it already exists, but */ /* it's more efficient to not bother even trying! */ if (!window_id) { RetError(toolbox_create_object(0, "SaveFile", &id)); } else id = window_id; /* Show it - the ToBeShown event does the rest */ RetError(toolbox_show_object(Toolbox_ShowObject_AsMenu, id, Toolbox_ShowObject_AtPointer, NULL, parent, -1)); return NULL; } /*************************************************/ /* savefile_to_be_shown() */ /* */ /* Fills in the Save File dialogue prior to */ /* being shown, on the basis of the parent */ /* component ID and ancestor object ID in the */ /* event. */ /* */ /* Parameters are as standard for a Toolbox */ /* event handler. */ /*************************************************/ int savefile_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle) { char text[Limits_OS_Pathname]; browser_data * b; int first_time = !window_id; int ok = 0; ObjectId pc = idb->parent_component; /* If this is the first time we've been opened, do a few */ /* initialisation bits and pieces */ if (first_time) { /* Process the writable icon text */ ChkError(windows_process_component_text(idb->self_id, SaveFileWrit, text, sizeof(text), 0, 1)); /* The following ensures that we increment the usage */ /* count for the dialogue so it is never deleted. */ /* Otherwise, we have to start keeping track of when */ /* it goes and deal with event handlers etc. as */ /* appropriate. Creating the object like this only */ /* gives back the existing ID of what is already */ /* there, so this works out as taking up less time */ /* and less code than an alternative approach. */ ChkError(toolbox_create_object(0, "SaveFile", NULL)); } /* Where did we come from? */ ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b)); /* If the parent ID is the same as a toolbar, this was */ /* from a toolbar button - in which case, use a parent */ /* ID of FileSaveParent, as that is what we actually */ /* want to do. */ if ( idb->parent_id == toolbars_get_upper(b) || idb->parent_id == toolbars_get_lower(b) ) pc = FileSaveParent; /* Deal with each parent menu item case */ switch (pc) { case -1: { /* We came from a keyboard shortcut, so want to save the */ /* current page source. */ pc = FileSaveFrame; /* Slight complication is that only an ancestor browser ever */ /* has the input focus, so we need to find the actual selected */ /* frame that the user is going to think the dialogue is for. */ if (b->selected_frame) b = b->selected_frame; /* No 'break' - let this drop through... */ } /* Save the page source */ case FileSaveFrame: /* Fall through to FileSaveParent */ case FileSaveParent: { /* For saving a frameset, work out the required browser_data struct */ if (pc == FileSaveParent) { if (b->parent && b->parent ->source) b = b->parent; else if (b->real_parent && b->real_parent->source) b = b->real_parent; else if (b->ancestor && b->ancestor ->source) b = b->ancestor; } /* Reset the transferred data counter */ b->save_transferred = 0; ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, browser_current_url(b))); /* Set the draggable sprite */ ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, b->page_is_text ? FileType_TEXT : FileType_HTML)); /* Remember various details about the dialogue's source */ savefile_browser = b; savefile_token = NULL; ok = 1; } break; #ifndef REMOTE_HOTLIST case HotlistSaveHotlist: { ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0))); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML)); ok = 1; } break; /* Save an object from the hotlist - a URL, directory, */ /* or general selection */ case MiscHotlistSaveObject: { hotlist_item * source = hotlist_find_selected_item(); unsigned int items = hotlist_count_selected_items(); if (items && source) { if (items == 1 && source->type == hl_url) { ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, source->data.url)); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI)); } else { ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0))); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML)); } ok = 1; } #ifdef TRACE else { erb.errnum = Utils_Error_Custom_Normal; sprintf(erb.errmess, "Should have more than a selected hotlist item %p and item count %d in savefile_to_be_shown", source, items); ChkError(&erb); } #endif } break; #endif case ExportAsDraw: { ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "(NotDone!)")); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_DRAW)); } break; case ExportAsText: { ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "(NotDone!)")); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_TEXT)); } break; /* Save the current location as a URI file */ case FileSaveCurrentLocation: { char * url = browser_current_url(b); if (!url) url = " "; ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, url)); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI)); savefile_browser = b; savefile_token = NULL; ok = 1; } break; /* Export the link the pointer was over when the menu opened */ case ExportLink: { HStream * link = menus_main_opened_over(); if (!link || !ISLINK(link)) { #ifndef TRACE return 0; #else erb.errnum = Utils_Error_Custom_Normal; sprintf(erb.errmess, "Menu token %p is not a link or has no anchor text in savefile_to_be_shown", link); show_error_ret(&erb); return 0; #endif } /* Set the leafname and filetype */ ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, link->anchor)); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI)); /* Remember various details about the dialogue's source */ savefile_browser = b; savefile_token = link; ok = 1; } break; case ExportPicture: { HStream * image = menus_main_opened_over(); if ( !image || ! ( (image->style & IMG) || ( image->tagno == TAG_INPUT && HtmlINPUTtype(image) == inputtype_IMAGE ) || ( image->tagno == TAG_OBJECT && object_token_is_image(b, image) ) ) ) { #ifndef TRACE return 0; #else erb.errnum = Utils_Error_Custom_Normal; sprintf(erb.errmess, "Menu token %p is not an image in savefile_to_be_shown", image); show_error_ret(&erb); return 0; #endif } if (image->style & IMG) { ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, image->src)); } else if (image->tagno == TAG_INPUT) { ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, (char *) HtmlINPUTsrc(image))); } else { ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, (char *) HtmlOBJECTdata(image))); } ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_SPR)); /* Remember various details about the dialogue's source */ savefile_browser = b; savefile_token = image; ok = 1; } break; case ExportBackground: { if (b->background_image == -1) { #ifndef TRACE return 0; #else erb.errnum = Utils_Error_Custom_Normal; StrNCpy0(erb.errmess, "There is no background image on this page in savefile_to_be_shown"); show_error_ret(&erb); return 0; #endif } ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "Background")); ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_SPR)); savefile_browser = b; savefile_token = NULL; ok = 1; } break; #ifdef TRACE default: { erb.errnum = 0; StrNCpy0(erb.errmess, "Save dialogue origin not understood in savefile_to_be_shown"); show_error_ret(&erb); return 0; } break; #endif } /* If we were successful, install relevant event handlers etc., assuming */ /* this is the first time the dialogue was opened. */ if (ok) { parent_component = pc; ancestor_id = b->self_id; if (first_time) { window_id = idb->self_id; ChkError(event_register_toolbox_handler(window_id, Draggable_DragEnded, savefile_drag_ended, NULL)); ChkError(event_register_toolbox_handler(window_id, ESaveFileOK, savefile_ok, NULL)); ChkError(event_register_toolbox_handler(window_id, ESaveFileCancel, savefile_cancel, NULL)); } } return 1; } /*************************************************/ /* savefile_set_leafname_from_url() */ /* */ /* Sets the leafname in the Save File dialogue */ /* from the given URL, preserving whatever */ /* path component may already have been present. */ /* */ /* Parameters: Object ID of the dialogue; */ /* */ /* Component ID of the writable */ /* gadget; */ /* */ /* Pointer to a null terminated URL */ /* to work with. */ /*************************************************/ _kernel_oserror * savefile_set_leafname_from_url(ObjectId object, ComponentId component, char * url) { _kernel_oserror * e; char * dot; char path[Limits_OS_Pathname]; /* See what filename is already in the dialogue */ path[0] = 0; /* Important so that when we strcat the leafname later, */ /* things will work even if the writable holds no text. */ RetError(writablefield_get_value(0, object, component, path, sizeof(path), NULL)); path[sizeof(path) - 1] = 0; /* If there's a leafname here, force a terminator in place of the '.' */ dot = strrchr(path, '.'); if (dot) *(++dot) = 0; else dot = path; /* So 'dot' points somewhere inside 'path' - tag on the leafname */ urlutils_leafname_from_url(url, dot, sizeof(path) - ((int) path - (int) dot)); /* Set the value */ return writablefield_set_value(0, object, component, path); } /*************************************************/ /* savefile_set_filetype() */ /* */ /* Sets the sprite of the draggable object in */ /* the Save File dialogue according to the given */ /* filetype, and records that filetype in */ /* savefile_type. */ /* */ /* If the sprite cannot be found in the Wimp */ /* sprite pool, 'file_xxx' is used instead. */ /* */ /* Parameters: Object ID of the dialogue; */ /* */ /* Component ID of the draggable */ /* gadget; */ /* */ /* The filetype. */ /*************************************************/ _kernel_oserror * savefile_set_filetype(ObjectId object, ComponentId component, int type) { char sprite[Limits_OS_SpriteName]; int len; savefile_type = type; /* Will it fit in the buffer? */ len = utils_len_printf("file_%03x", type); /* If so, build the sprite name and tell the gadget to use that sprite */ if (len < sizeof(sprite)) { sprintf(sprite, "file_%03x", type); if ( !_swix(Wimp_SpriteOp, _IN(0) | _IN(2), 0x18, /* Select sprite */ sprite) ) { /* If it has been found, use this sprite */ return draggable_set_sprite(0, object, component, sprite); } else { /* Otherwise use the generic 'file_xxx' instead */ return draggable_set_sprite(0, object, component, "file_xxx"); } } return NULL; } /*************************************************/ /* savefile_drag_ended() */ /* */ /* Handle Draggable_DragEnded events from the */ /* Save File dialogue draggable sprite gadget. */ /* */ /* Parameters are as standard for a Toolbox */ /* event handler. */ /*************************************************/ static int savefile_drag_ended(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle) { DraggableDragEndedEvent * drag = (DraggableDragEndedEvent *) event; protocols_saving saving = protocols_saving_nothing; int size = 4096; /* More or less arbitrary */ WimpGetPointerInfoBlock info; int window_handle; char path[Limits_OS_Pathname]; char * leaf; void * extra = savefile_token; /* If the user dragged back to the save dialogue, do nothing */ ChkError(window_get_wimp_handle(0, window_id, &window_handle)); if (window_handle == drag->window_handle) return 1; /* Get the pathname from the Save File dialogue. */ ChkError(writablefield_get_value(0, window_id, SaveFileWrit, path, sizeof(path), NULL)); path[sizeof(path) - 1] = 0; /* Point to the leafname component */ leaf = strrchr(path, '.'); if (!leaf) leaf = path; else leaf ++; /* Work out the estimated data size */ switch (parent_component) { /* Size of an HTML file */ case FileSaveFrame: case FileSaveParent: { saving = protocols_saving_document_source; if (is_known_browser(savefile_browser)) size = save_source_size(savefile_browser); else size = 0; } break; /* Size of a URI file for the current location */ case FileSaveCurrentLocation: { char * url = browser_current_url(savefile_browser); if (!url) url = " "; saving = protocols_saving_link; size = save_uri_size(url, 0); } break; /* Size of a URI file */ case ExportLink: { saving = protocols_saving_link; if (savefile_token && savefile_token->anchor) size = save_uri_size(savefile_token->anchor, 0); else size = 0; } break; /* Size of an image to export */ case ExportPicture: { saving = protocols_saving_image_sprite; if (savefile_token && is_known_browser(savefile_browser)) size = image_sprite_size(savefile_browser, savefile_token); else size = 0; } break; /* Save a background image */ case ExportBackground: { saving = protocols_saving_image_sprite; if (is_known_browser(savefile_browser)) size = image_sprite_size(savefile_browser, NULL); else size = 0; } break; #ifndef REMOTE_HOTLIST /* Saving the entire hotlist */ case HotlistSaveHotlist: { saving = protocols_saving_entire_hotlist; } break; /* Save an object from the hotlist - a URL, directory, */ /* or general selection */ case MiscHotlistSaveObject: { hotlist_item * source = hotlist_find_selected_item(); unsigned int items = hotlist_count_selected_items(); if (items && source) { if (items == 1 && source->type == hl_url) { saving = protocols_saving_hotlist_entry; extra = (void *) source; size = save_uri_size(source->data.url, 0); } else saving = protocols_saving_hotlist_selection; } } break; #endif /* For others, could leave size as 4096 - *but must set 'saving' appropriately* */ } /* Send out the DataSave message */ info.x = drag->x; info.y = drag->y; info.window_handle = drag->window_handle; info.icon_handle = drag->icon_handle; ChkError(protocols_atats_send_data_save(savefile_browser, extra, leaf, size, savefile_type, saving, &info)); return 1; } /*************************************************/ /* savefile_ok() */ /* */ /* Handles clicks on the 'OK' button in the */ /* Save File dialogue. */ /* */ /* Parameters are as standard for a Toolbox */ /* event handler. */ /*************************************************/ static int savefile_ok(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle) { return 1; } /*************************************************/ /* savefile_cancel() */ /* */ /* Handles clicks on the 'Cancel' button in the */ /* Save File dialogue */ /* */ /* Parameters are as standard for a Toolbox */ /* event handler. */ /*************************************************/ static int savefile_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle) { /* If we were fetching, stop the fetch */ if (savefile_browser && is_known_browser(savefile_browser)) { if (savefile_browser->save_link) fetch_stop(savefile_browser, 0); } /* We don't do anything sophisticated, like */ /* restoring previous options here, as the */ /* dialogue is too simple for it to be */ /* worthwhile. Therefore, just close it. */ ChkError(savefile_close(0,0)); return 1; } /*************************************************/ /* savefile_close() */ /* */ /* If the Save File dialogue is opened, this */ /* will close it. */ /* */ /* Parameters: An object ID, or 0. If not zero, */ /* the ID must match the ancestor */ /* recorded when the dialogue was */ /* opened or no action is taken. */ /* */ /* 0 to close the dialogue, 1 to do */ /* everything except that. */ /*************************************************/ _kernel_oserror * savefile_close(ObjectId ancestor, int do_not_close) { _kernel_oserror * e = NULL; if (!window_id) return NULL; if (ancestor && ancestor != ancestor_id) return NULL; /* If required, close the dialogue */ if (!do_not_close && !e) { /* If the dialogue came from a menu tree, collapse the tree */ if (parent_component != -1) e = _swix(Wimp_CreateMenu, _IN(1), -1); /* Close the dialogue */ if (!e) e = toolbox_hide_object(0, window_id); } parent_component = -1; ancestor_id = 0; savefile_browser = NULL; savefile_token = NULL; savefile_type = 0x000; return e; } /*************************************************/ /* savefile_return_dialogue_info() */ /* */ /* Returns information on the Save File dialogue */ /* and its ancestor. */ /* */ /* Parameters: Pointer to an ObjectId, in which */ /* the ID of the dialogue is placed; */ /* */ /* Pointer to an ObjectId, in which */ /* the ID of the ancestor window is */ /* placed. */ /* */ /* Returns: See parameters list, and note */ /* that the returned values will be */ /* 0 and 0 if the Save File dialogue */ /* is closed. */ /* */ /* Assumes: Either pointer may be NULL. */ /*************************************************/ void savefile_return_dialogue_info(ObjectId * window, ObjectId * ancestor) { if (window) *window = window_id; if (ancestor) *ancestor = ancestor_id; }