/* Copyright 1998 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: c.main */ /* Purpose: Interactive help application. */ /* Author: Richard Leggett */ /* History: 12-Dec-97: RML: Created. */ /* 05-Jan-97: RML: Released current version with functional spec. */ /* 13-Jan-97: RML: Updated after spec review. */ /* 10-Feb-98: RML: Now unsets Help$Dir upon exit and checks it upon */ /* start-up. */ /* 16-Feb-98: RML: New WIMP message Message_HelpEnable introduced. */ /* */ /* Copyright 1998 Acorn Computers Ltd., Cambridge, UK. */ /*---------------------------------------------------------------------------*/ #include #include #include #include "tboxlibs/wimp.h" #include "swis.h" #include "tboxlibs/wimplib.h" #include "tboxlibs/toolbox.h" #include "tboxlibs/event.h" #include "tboxlibs/window.h" #include "tboxlibs/iconbar.h" #include "tboxlibs/proginfo.h" #include "tboxlibs/menu.h" #include "help.h" #include "common.h" #include "defines.h" /* Toolbox/WIMP globals */ static MessagesFD messages_desc; /* Messagetrans */ static char messages_string[255]; /* Messagetrans */ static IdBlock idb; /* Toolbox ID block */ static int task_handle; /* Handle of this task */ static int help_box_id; /* Toolbox ID of the help box */ static int help_box_wimp; /* WIMP handle of the help box */ static int iconbar_id; /* Toolbox ID for the icon bar icon */ static int iconbar_menu_id; /* Toolbox ID for the icon bar menu */ static int proginfo_id; /* Toolbox ID for the program info dialogue */ static int shadow_id; /* ID of the shadow object */ static int shadow_wimp; /* Wimp handle of the shadow */ /* Configuration variables */ static int options = 0; /* Option flags */ static int help_on = 1; /* Set to 1 for help on, 0 for help suspended */ static int help_box_showing = 0; /* Set if there is a help box on screen */ static int line_spacing = 32; /* Line spacing of help text lines */ static int still_distance = 32; /* Distance mouse may move, but still be considered still */ static int delay = DefaultDelay; /* Delay between help requests */ static int helpbox_font_handle = -1; /* font handle of the help text */ static int timeout = DefaultTimeout; /* timeout time */ static int fontsize; /* the chosen font size */ static char fontname[256]; /* the chosen font name */ /* Status variables */ static int screen_w, screen_h; /* Width and height of screen in the current mode */ static int request_handle=0; /* Task handle of app that last help request was sent to */ static WimpGetPointerInfoBlock request_pi; /* Pointer location + icon that help request was last sent to */ static int reply_time=-1, reply_x=-1, reply_y=-1;/* The time and position of the last help reply received */ static int check_time=0, check_x=-1, check_y=-1; /* The time & position of the last check */ static int still_time=0; /* How long the pointer has been still for */ static int num_icons=-1; /* Current number of icons in the help box */ static char help_box_message[1024]; /* Current message being displayed in help box */ /*---------------------------------------------------------------------------* * program_exit * * * * Finished with this program, so exit. * *---------------------------------------------------------------------------*/ static void program_exit(void) { _kernel_oscli("Unset Help$Dir"); exit(0); } /*---------------------------------------------------------------------------* * read_options * * * * Read in the options from the Config file * * * * Globals: delay, options * *---------------------------------------------------------------------------*/ static void read_options(void) { FILE *fp; char *item; char *value; char buffer[256]; int colon, fh; int more = 1; /* Setup default options */ strcpy(fontname, DefaultFont); fontsize = DefaultFontsize; delay = DefaultDelay; options = DefaultOptions; still_distance = -1; timeout = DefaultTimeout; #if UseChoicesFile /* Open configuration file */ fp = fopen(ConfigFile, "r"); if (fp) { /* Found config file */ options = 0; while (more) { /* Get the next line from the file */ more = (int)fgets(buffer, 256, fp); if (more) { /* Check for a colon in this line, indicating it's a setting */ colon = strstr(buffer, ":") - buffer; if (colon>0) { /* Break the string into item (eg. 'Font') and value (eg. 'Homerton.Medium') */ buffer[colon] = 0; item = buffer; value = item + colon + 1; value[strlen(value)-1] = 0; if (strcmp(item, FontConfig)==0) strcpy(fontname, value); else if (strcmp(item, SizeConfig)==0) fontsize = atoi(value); else if (strcmp(item, DelayConfig)==0) delay = atoi(value); else if (strcmp(item, StillConfig)==0) still_distance = atoi(value); else if (strcmp(item, TimeoutConfig)==0) timeout = atoi(value); else if (strcmp(item, CommonConfig)==0 && strcmp(value, Yes)==0) options += Option_HideCommon; else if (strcmp(item, ShadowConfig)==0 && strcmp(value, Yes)==0) options += Option_DropShadow; else if (strcmp(item, RepeatConfig)==0 && strcmp(value, Yes)==0) options += Option_Repeat; } } } fclose(fp); } #endif /* Check font is available */ if (_swix(Font_FindFont, _INR(1,5)|_OUT(0), fontname, 16*16, 16*16, 0, 0, &fh)) { strcpy(fontname, DefaultFont); } else { _swix(Font_LoseFont, _IN(0), fh); } /* Check bounds */ if (delay<0 || delay>MaxDelay) { delay = DefaultDelay; } if (fontsize<4 || fontsize>144) { fontsize = DefaultFontsize; } if (still_distance<0 || still_distance>64) { still_distance = read_cmos_value(WimpDoubleClickMove) ^ 32; } helpbox_font_handle = help_font_initialise(fontname, fontsize, &line_spacing); } /*---------------------------------------------------------------------------* * update_help_box * * * * Called when there's a new help message. A width is calculated and then * * the message broken down into lines. Each line is represented by an icon * * within the box (we let the wimp do the redrawing). * * * * In: string -> help message. * * length = length of help message. * * * * Globals: num_icons, reply_x, reply_y * *---------------------------------------------------------------------------*/ static _kernel_oserror* update_help_box(char* string, int length) { WimpOpenWindowBlock win; WimpDeleteIconBlock wdi; WimpCreateIconBlock wci; char* expanded_string; char* buffer; char* buffer_ptr; char valid_string[]=""; BBox extent; int x, y, num_strings, lines, i, width_os, height_os; int width, bullet_size; if (length>1024) return common_error(messages_lookup("Error4")); strcpy(help_box_message, string); reply_x = request_pi.x; reply_y = request_pi.y; _swix(OS_ReadMonotonicTime, _OUT(0), &reply_time); if (reply_time==-1) reply_time = 0; /* Avoid getting confused with the reset value */ /* Find the size of the ' ' in the helpbox font */ _swix(Font_ConverttoOS, _INR(1, 2)|_OUT(1), return_string_length(helpbox_font_handle, " "), 0, &bullet_size); error_trap(toolbox_hide_object(0, help_box_id), 0); error_trap(toolbox_hide_object(0, shadow_id), 0); /* Get memory to store expanded help message */ buffer = malloc(1024 + 1024); if (!buffer) return common_error(messages_lookup("Error4")); expanded_string = &buffer[1024]; /* Break the help message down into lines which will be displayed in the box */ num_strings = translate_help_string(help_box_message, expanded_string, length); width = choose_box_width(helpbox_font_handle, &width_os, &height_os); lines = break_strings_down(helpbox_font_handle, width, buffer); /* Set the extent of the help box (and shadow) */ extent.xmin = 0; extent.xmax = width_os; extent.ymin = 0; extent.ymax = height_os; error_trap(window_set_extent(0, help_box_id, &extent), 0); error_trap(window_set_extent(0, shadow_id, &extent), 0); /* Delete old icons */ for (i=0; i (reply_y - PointerHeight) ) { y = reply_y + height_os; } else { y = reply_y - PointerHeight; } if ( width_os > (screen_w - reply_x) ) { x = reply_x - width_os - 16; } else { x = reply_x + PointerWidth; } /* Re-open the window with it's new extent */ win.visible_area.xmin = x; win.visible_area.xmax = x + width_os; win.visible_area.ymin = y - height_os; win.visible_area.ymax = y; win.xscroll = 0; win.yscroll = 0; win.behind = -1; if (options & Option_DropShadow) { win.window_handle = shadow_wimp; error_trap(wimp_open_window(&win), 0); win.visible_area.xmin -= DropShadowSize; win.visible_area.xmax -= DropShadowSize; win.visible_area.ymin += DropShadowSize; win.visible_area.ymax += DropShadowSize; } win.window_handle = help_box_wimp; error_trap(wimp_open_window(&win), 0); help_box_showing = 1; free(buffer); return NULL; } /*---------------------------------------------------------------------------* * remove_help_box * * * * Removes the help box from the screen. * * * * Globals: reply_x, reply_y, help_box_showing * *---------------------------------------------------------------------------*/ static void remove_help_box(void) { error_trap(toolbox_hide_object(0, help_box_id), 0); error_trap(toolbox_hide_object(0, shadow_id), 0); reply_time = reply_x = reply_y = -1; help_box_showing = 0; } /*---------------------------------------------------------------------------* * check_new_help_message * * * * De-tokenise a new help message and decide if the help box needs updating. * * * * In: message -> help message. * *---------------------------------------------------------------------------*/ static void check_new_help_message(char* message) { char help_reply[1024]; int ptr, end; if (strlen(message)==0) return; /* Translate (de-tokenise) string returned to us */ _swix(OS_GSTrans, _INR(0,2)|_OUT(0)|_OUT(2), message, help_reply, 1024+(1u<<31), &ptr, &end); help_reply[end] = 0; /* Deal with help message */ if (help_box_showing) { /* If the help box is on the screen, then only update it if the message is different */ if (strcmp(help_reply, help_box_message) != 0) { error_trap(update_help_box(help_reply, end), 0); } } else { /* If help box is not on screen... */ if (options & Option_Repeat) { /* If repeat option is on, then display box regardless */ error_trap(update_help_box(help_reply, end), 0); } else { /* If repeat option is not on, then only display box if message is different from previous */ if (strcmp(help_reply, help_box_message) != 0) { error_trap(update_help_box(help_reply, end), 0); } } } return; } /*---------------------------------------------------------------------------* * send_help_request * * * * Sends a help request out. * * * * In: gpi -> block returned by Wimp_GetPointerInfo * * * * Globals: request_pi * *---------------------------------------------------------------------------*/ static void send_help_request(WimpGetPointerInfoBlock* gpi) { WimpSendMessageBlock msg; char help_string[256]; char token[12]; int icon = gpi->icon_handle; /* Don't bother sending requests to ourself */ if ( (gpi->window_handle == help_box_wimp) || (gpi->window_handle == shadow_wimp) ) return; /* Don't bother sending if the mouse state hasn't changed since the last request - otherwise the constant message traffic will prevent the Wimp from lowering the CPU speed Note that we check the entire state, just in case the window/icon has changed without the mouse moving (e.g. close icon clicked) */ if ( !memcmp(&request_pi,gpi,sizeof(WimpGetPointerInfoBlock)) ) return; request_pi = *gpi; if ( (icon<=-2) && (icon>=-14) ) { /* Icon numbers -2 to -14 are window tool icons - we supply the help ourselves */ if (options & Option_HideCommon) return; switch (icon) { case -4: sprintf(help_string, messages_lookup("HelpI4")); strcat(help_string, messages_lookup("HelpI4a")); strcat(help_string, messages_lookup("HelpI4b")); break; case -5: sprintf(help_string, messages_lookup("HelpI5")); strcat(help_string, messages_lookup("HelpI5a")); break; default: sprintf(token, "HelpI%d", -icon); sprintf(help_string, messages_lookup(token)); break; } /* Send this string away to a function which will decide if to update the help box */ check_new_help_message(help_string); } else { /* Not a window tool, so send a help request message */ msg.hdr.size = 40; msg.hdr.your_ref = 0; msg.hdr.action_code = Wimp_MHelpRequest; memcpy(&msg.data.help_request, gpi, sizeof(WimpGetPointerInfoBlock)); error_trap(wimp_send_message(18, &msg, gpi->window_handle, gpi->icon_handle, &request_handle), 0); /* We're only bothered about the pointer was over the icon bar */ if (gpi->window_handle!=-2) request_handle=0; } } /*---------------------------------------------------------------------------* * toggle_onoff_status * * * * Toggle the help on/off status. Depending on the new value, tick/untick * * the Suspend menu item and grey/ungrey the icon bar icon. * * * * Globals: help_on * *---------------------------------------------------------------------------*/ static void toggle_onoff_status(void) { help_on = help_on ^ 1; if (help_on) { /* Help has been turned on */ memset(help_box_message, 0, sizeof(help_box_message)); error_trap(iconbar_set_sprite(0, iconbar_id, "!help"), 0); error_trap(menu_set_tick(0, iconbar_menu_id, MenuItemSuspend, 0), 0); error_trap(event_set_mask(Wimp_Poll_RedrawWindowRequestMask + Wimp_Poll_PointerLeavingWindowMask + Wimp_Poll_PointerEnteringWindowMask ), 0); } else { /* Help has been turned off - remove window if open */ remove_help_box(); error_trap(iconbar_set_sprite(0, iconbar_id, "lo!help"), 0); error_trap(menu_set_tick(0, iconbar_menu_id, MenuItemSuspend, 1), 0); error_trap(event_set_mask(Wimp_Poll_NullMask + Wimp_Poll_RedrawWindowRequestMask + Wimp_Poll_PointerLeavingWindowMask + Wimp_Poll_PointerEnteringWindowMask ), 0); } } /*---------------------------------------------------------------------------------------------------------*/ /* EVENT HANDLERS */ /*---------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* * iconbar_click * * * * A click on the icon bar. * *---------------------------------------------------------------------------*/ static int iconbar_click(int event_code, ToolboxEvent *event_block, IdBlock *id_block, void *handle) { /* Toggle help on and off */ if (event_block->hdr.flags & 4) toggle_onoff_status(); return 1; } /*---------------------------------------------------------------------------* * iconbar_menu_select * * * * An item has been selected from the icon bar menu. * *---------------------------------------------------------------------------*/ static int iconbar_menu_select(int event_code, ToolboxEvent *event_block, IdBlock *id_block, void *handle) { int gadget=id_block->self_component; switch (gadget) { case MenuItemQuit: program_exit(); break; case MenuItemSuspend: toggle_onoff_status(); break; } return 1; } /*---------------------------------------------------------------------------* * help_reply * * * * We've received a reply from the last task a help request was sent to. * *---------------------------------------------------------------------------*/ static int help_reply(WimpMessage *event, void *handler) { char* taskname; /* Check for hide common option */ if (options & Option_HideCommon) { /* Hide common is on, so find the task name of the message reply. If it's Filer or Pinboard, don't bother to display the message. */ _swix(TaskManager_TaskNameFromHandle, _IN(0)|_OUT(0), event->hdr.sender, &taskname); if ( (strcmp(taskname, "Filer")==0) || (strcmp(taskname, "Pinboard")==0) ) return 1; } /* Send this string away to a function which will decide if to update the help box */ check_new_help_message(event->data.help_reply.text); return 1; } /*---------------------------------------------------------------------------* * mode_change * * * * Screen mode has changed - reread the screensize. * * * * Globals: screen_w, screen_h * *---------------------------------------------------------------------------*/ static int mode_change(WimpMessage *event, void *handler) { common_read_screensize(&screen_w, &screen_h); /* There's a chance the eigen values changed,regrab the font */ remove_help_box(); if (helpbox_font_handle != -1) { _swix(Font_LoseFont, _IN(0), helpbox_font_handle); helpbox_font_handle = help_font_initialise(fontname, fontsize, &line_spacing); } helpbox_font_handle = help_font_initialise(fontname, fontsize, &line_spacing); return 1; } /*---------------------------------------------------------------------------* * help_enable * * * * Message_HelpEnable received. * *---------------------------------------------------------------------------*/ static int help_enable(WimpMessage *event, void *handler) { /* Do we disable or enable Help? */ if (event->data.help_enable.flags & Wimp_MHelpEnable_Disable) { if (help_on) toggle_onoff_status(); } else { if (!help_on) toggle_onoff_status(); } /* Do we re-read settings? */ if (event->data.help_enable.flags & Wimp_MHelpEnable_ReadSettings) { if (helpbox_font_handle != -1) { _swix(Font_LoseFont, _IN(0), helpbox_font_handle); } read_options(); update_help_box(help_box_message, strlen(help_box_message)); } return 1; } /*---------------------------------------------------------------------------* * message_ack * * * * An acknowledgement message means that an application which was sent a * * help request message has failed to provide a help reply message. * * * * Globals: request_handle * *---------------------------------------------------------------------------*/ static int message_ack(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle) { char* taskname; char string[256]; if (event->user_message_acknowledge.hdr.action_code != Wimp_MHelpRequest) return 1; if (request_handle==0) return 1; /* Find the name of the task and provide a 'This is the ... icon' */ _swix(TaskManager_TaskNameFromHandle, _IN(0)|_OUT(0), request_handle, &taskname); strcpy(string, messages_lookup_with_parameter("HelpH3", taskname)); check_new_help_message(string); request_handle=0; return 1; } /*---------------------------------------------------------------------------* * null_event * * * * Null event handler. We send our help requests from here. * * * * Globals: check_time, check_x, check_y * *---------------------------------------------------------------------------*/ static int null_event(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle) { WimpGetPointerInfoBlock gpi; int time; if (!help_on) return 1; error_trap(wimp_get_pointer_info(&gpi), 0); /* Check if pointer has moved from the position it was when the help box was popped up */ if ( (gpi.x < reply_x - still_distance) || (gpi.x > reply_x + still_distance) || (gpi.y < reply_y - still_distance) || (gpi.y > reply_y + still_distance) ) { remove_help_box(); } _swix(OS_ReadMonotonicTime, _OUT(0), &time); /* Check for timeout */ if ( (reply_time != -1) && (timeout > 0) && (time - reply_time > timeout) ) { remove_help_box(); still_time = 0; } /* We check the position of the pointer every n centiseconds */ if ( time - check_time > DelayBetweenChecks ) { /* Have we moved since the last check? */ if ( (check_x >= gpi.x-still_distance) && (check_x <= gpi.x+still_distance) && (check_y >= gpi.y-still_distance) && (check_y <= gpi.y+still_distance) ) { /* No move, so add the delay since last check to still_time */ still_time += (time - check_time); } else { /* Yes - we moved, so reset still_time */ still_time = 0; } check_time = time; check_x = gpi.x; check_y = gpi.y; /* If we've been stopped for long enough, send a help request */ if (still_time > delay) send_help_request(&gpi); } return 1; } /*---------------------------------------------------------------------------* * key_event * * * * Key pressed handler. Just pass them on. * *---------------------------------------------------------------------------*/ static int key_event(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle) { error_trap(wimp_process_key(event->key_pressed.key_code), 0); return 1; } /*---------------------------------------------------------------------------* * message_quit * * * * Called when wimp quit message received. * *---------------------------------------------------------------------------*/ static int message_quit(WimpMessage *event, void *handler) { program_exit(); return 1; } /*---------------------------------------------------------------------------------------------------------*/ /* INITIALISATION */ /*---------------------------------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* * initialise_toolbox_objects * * * * Create and link toolbox objects. * *---------------------------------------------------------------------------*/ static void initialise_toolbox_objects(void) { /* Create objects */ error_trap(toolbox_create_object(0, "Iconbar", &iconbar_id), 1); error_trap(toolbox_create_object(0, "HelpBox", &help_box_id), 1); error_trap(toolbox_create_object(0, "Shadow", &shadow_id), 1); error_trap(toolbox_create_object(0, "ProgInfo", &proginfo_id), 1); error_trap(toolbox_create_object(0, "IconMenu", &iconbar_menu_id), 1); /* Get relevant WIMP handles */ error_trap(window_get_wimp_handle(0, help_box_id, &help_box_wimp), 0); error_trap(window_get_wimp_handle(0, shadow_id, &shadow_wimp), 0); /* Link objects */ error_trap(iconbar_set_menu(0, iconbar_id, iconbar_menu_id), 0); error_trap(menu_set_sub_menu_show(0, iconbar_menu_id, MenuItemInfo, proginfo_id), 0); /* Register handlers */ error_trap(event_register_message_handler(Wimp_MQuit, message_quit, NULL), 0); error_trap(event_register_message_handler(Wimp_MHelpReply, help_reply, NULL), 0); error_trap(event_register_message_handler(Wimp_MModeChange, mode_change, NULL), 0); error_trap(event_register_message_handler(Wimp_MHelpEnable, help_enable, NULL), 0); error_trap(event_register_wimp_handler(-1, Wimp_ENull, null_event, NULL), 0); error_trap(event_register_wimp_handler(-1, Wimp_EUserMessageAcknowledge, message_ack, NULL), 0); error_trap(event_register_wimp_handler(-1, Wimp_EKeyPressed, key_event, NULL), 0); error_trap(event_register_toolbox_handler(iconbar_menu_id, Menu_Selection, iconbar_menu_select, NULL), 0); error_trap(event_register_toolbox_handler(iconbar_id, Iconbar_Clicked, iconbar_click, NULL), 0); /* Set program version */ error_trap(proginfo_set_version(0, proginfo_id, messages_lookup("_Version")), 0); } /*---------------------------------------------------------------------------* * main_initialise * * * * Initialisation. * * * * Globals: screen_w, screen_h * *---------------------------------------------------------------------------*/ static int main_initialise(void) { static int messages_wanted[] = {Wimp_MQuit, Wimp_MHelpReply, Wimp_MModeChange, Wimp_MHelpEnable, 0}; static int tbox_wanted[] = {Menu_Selection, Iconbar_Clicked, 0}; common_read_screensize(&screen_w, &screen_h); messages_register(&messages_desc, messages_string); memset(help_box_message, 0, sizeof(help_box_message)); error_trap(toolbox_initialise(0, 310, messages_wanted, tbox_wanted, "", &messages_desc, &idb, 0, &task_handle, 0), 1); error_trap(event_initialise(&idb), 0); error_trap(event_set_mask(Wimp_Poll_RedrawWindowRequestMask + Wimp_Poll_PointerLeavingWindowMask + Wimp_Poll_PointerEnteringWindowMask ), 0); initialise_toolbox_objects(); read_options(); return 1; } /*---------------------------------------------------------------------------* * main * *---------------------------------------------------------------------------*/ int main(int argc, char **argv) { WimpPollBlock wpb; int event_code; if (!main_initialise()) return 0; while (TRUE) { if (help_on) { unsigned int time; _swix(OS_ReadMonotonicTime, _OUT(0), &time); error_trap(event_poll_idle(&event_code, &wpb, time + NullPollRate, NULL), 0); } else { error_trap(event_poll(&event_code, &wpb, NULL), 0); } } return 0; }