blob: b74e64093bf92527437cd8e2b95d06e0927c1cee [file] [log] [blame] [edit]
/* *
* Easygl Version 2.0.1 *
* Written by Vaughn Betz at the University of Toronto, Department of *
* Electrical and Computer Engineering, with additions by Paul Leventis *
* and William Chow of Altera, Guy Lemieux of the University of Brish *
* Columbia, and Long Yu (Mike) Wang of the University of Toronto. *
* All rights reserved by U of T, etc. *
* *
* You may freely use this graphics interface for non-commercial purposes *
* as long as you leave the author info above in it. *
* *
* Revision History: *
* *
* V2.0.2 May 2013 - June 2013 (Mike Wang) *
* - In Win32, removed "Window" operation with right mouse click to align *
* with X11. *
* - Fixed a bug in Win32 where when the "Window" button is clicked, if the *
* application window gets minimized then returned back up, the screen *
* background would not be redrawn before new rubber band gets drawn. *
* - Grouped file scope variables into structures, factored out functions *
* that were too long, and formatted code spacings etc. *
* - Cleaned up Win32 code by removing the inefficient saving and updating *
* of graphics contexts. *
* - Added zooming with respect to cursor location. *
* - Added panning with middle mouse button (or mousewheel) click and drag. *
* - Ran experiment to test the impact of rect_off_screen() on drawscreen() *
* runtime. Found rect_off_screen() to be effective in speeding up screen *
* redraw. *
* - Added zooming using mousewheel. *
* *
* V2.0.1 Sept. 2012 (Vaughn Betz) *
* - Fixed a bug in Win32 where postscript output would make the graphics *
* crash when you redrew. *
* - Made a cleaner makefile to simplify platform selection. *
* - Commented and reorganized some of the code. Started cleaning up some *
* of the win32 code. Win32 looks inefficient; it is saving and updating *
* graphics contexts all the time even though we know when the context is *
* valid vs. not-valid. *
* TODO: make win32 work more like X11 (minimize gc updates). *
* *
* V2.0: Nov. 21, 2011 (Vaughn Betz) *
* - Updated example code, and some cleanup and bug fixes to win32 code. *
* - Removed some win32 code that had no X11 equivalent or wasn't well *
* documented. *
* - Used const char * where appropriate to get rid of g++ warnings. *
* - Made interface to things like xor drawing more consistent, and added *
* to example program. *
* - Made a simpler (easygl.cpp) interface to the graphics library for *
* use by undergraduate students. *
* *
* V1.06 : July 23, 2003 : (Guy Lemieux) *
* - added some typecasts to cleanly compile with g++ and MS c++ tools *
* - if WIN32 not defined, it defines X11 automatically *
* - fixed X11 compilation; WIN32 broke some things *
* *
* V1.05 : July 26, 2001 : (William) *
* - changed keyboard detect function to accept an int (virtual key) *
* *
* V1.04 : June 29, 2001 : (William) *
* - added drawcurve(), fillcurve() using Bezier curves *
* (support WIN32 screen / ps) *
* - added pt on object capability : using a memory buffer to draw an *
* graphics objects, then query if a point fall on the object (bear the *
* object's colour) : object_start(), object_end(), pt_on_object() *
* - added drawellipticarc(), fillellipticarc() *
* - added findfontsize() to help find a pointsize of a given height *
* - extended t_report to keep xleft, xright, ytop, ybot *
* - added update_window() to set the window bb *
* *
* V1.03 : June 18, 2001 : (William) *
* - added change_button_text() *
* *
* V1.02 : June 13, 2001 : (William) *
* - extension to mouse click function : can tell if ctrl/shift keys are *
* pressed *
* *
* V1.01 : June 1, 2001 : (William) *
* - add tooltip support *
* *
* V1.0 : May 14, 2001 : (William) *
* - fixed a problem with line styles, initial release on the internet *
* *
* March 27, 2001 : (William) *
* - added setcolor_by_colorref to make more colors available (in Win32) *
* *
* February 16, 2001 : (William) *
* - added quick zoom using right mouse clicks *
* *
* February 11, 2001 : (William) *
* - can define cleanup(), passed in when calling init_graphics(), and *
* called when shutting down *
* *
* February 1, 2001 : (William) *
* - fix xor mode redraw problem *
* *
* September 19, 2000 : (William) *
* - can define mouse_move callback function *
* - can add separators in between buttons *
* *
* September 8, 2000 : (William) *
* - added result_structure(), *
* - can define background color in init_graphics *
* *
* August 10, 2000 : (William Chow, choww@eecg.utoronto.ca) *
* - Finished all Win32 support functions *
* - use XOR mode for window zooming box *
* - added double buffering feature *
* *
* January 12, 1999: (Paul) *
* - Fixed a bunch of stuff with the Win32 support (memory leaks, etc) *
* - Made the clipping function using the update rectangle for Win32 *
* *
* January 9, 1999: (Paul Leventis, leventi@eecg.utoronto.ca) *
* - Added Win32 support. Should work under Windows98/95/NT 4.0/NT 5.0. *
* - Added a check to deselect_all to determine whether the screen needs to *
* be updated or not. Should elminate flicker from mouse clicks *
* - Added win32_invalidate_screen() call to graphics.c that in turn calls *
* update_screen, so this function was made non-static and added to the *
* header file. This is due to differences in the structure of Win32 *
* windowing apps. *
* - Win32 needs clipping (though done automatically, could be faster) *
* *
* Sept. 19, 1997: Incorporated Zoom Fit code of Haneef Mohammed at *
* Cypress. Makes it easy to zoom to a full view of the graphics. *
* *
* Sept. 11, 1997: Added the create_and destroy_button interface to *
* make it easy to add and destroy buttons from user code. Removed the *
* bnum parameter to the button functions, since it wasn't really needed. *
* *
* June 28, 1997: Added filled arc drawing primitive. Minor modifications *
* to PostScript driver to make the PostScript output slightly smaller. *
* *
* April 15, 1997: Added code to init_graphics so it waits for a window *
* to be exposed before returning. This ensures that users of non- *
* interactive graphics can never draw to a window before it is available. *
* *
* Feb. 24, 1997: Added code so the package will allocate a private *
* colormap if the default colormap doesn't have enough free colours. *
* *
* June 28, 1996: Converted all internal functions in graphics.c to have *
* internal (static) linkage to avoid any conflicts with user routines in *
* the rest of the program. *
* *
* June 12, 1996: Added setfontsize and setlinewidth attributes. Added *
* pre-clipping of objects for speed (and compactness of PS output) when *
* graphics are zoomed in. Rewrote PostScript engine to shrink the output *
* and make it easier to read. Made drawscreen a callback function passed *
* in rather than a global. Graphics attribute calls are more efficient -- *
* they check if they have to change anything before doing it. *
* *
* October 27, 1995: Added the message area, a callback function for *
* interacting with user button clicks, and implemented a workaround for a *
* Sun X Server bug that misdisplays extremely highly zoomed graphics. *
* *
* Jan. 13, 1995: Modified to incorporate PostScript Support. */
/**************************** Top-level summary ******************************
This graphics package provides API for client program that wishes to implement
a graphical user interface. The client program will first call init_graphics()
for initial setup, which includes opening up an application window, setting up
the specified window_name and background colour, creating sublevel windows,
etc. Then the program will call a few more setup functions such as
init_world(), which sets up world coordinates, create_button(), which sets up
menu buttons, and update_message(), which updates status bar message. After
all the setup, the program will call the main routine for the graphics,
event_loop(), which will take control of the program until the "Proceed"
button is pressed. Event_loop() directly communicates with X11/Win32 to
receive event notifications, and it either calls other event-handling
functions in the graphics package or callbacks passed as function pointers
from the client program. The most important callback function which the
client should provide is drawscreen(). Whenever the graphics need to be
redrawn, drawscreen() will be called. There are a number drawing routines
which the client can use to update the graphic contexts (ie. setcolor,
setfontsize, setlinewidth, and setlinestyle) and draw simple shapes as
desired. When the program is done executing the graphics, it should call
close_graphics() to release all drawing structures and close the graphics.*/
#ifndef NO_GRAPHICS // Strip everything out and just put in stubs if NO_GRAPHICS defined
/**********************************
* Common Preprocessor Directives *
**********************************/
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <algorithm>
#include "graphics.h"
using namespace std;
#if defined(X11) || defined(WIN32)
// VERBOSE is very helpful for developing event handling features. Outputs
// useful information when user interacts with the graphic interface.
// Uncomment the line below to turn on VERBOSE.
// #define VERBOSE
#define MWIDTH 104 /* Width of menu window */
#define MENU_FONT_SIZE 12 /* Font for menus and dialog boxes. */
#define T_AREA_HEIGHT 24 /* Height of text window */
#define MAX_FONT_SIZE 24 /* Largest point size of text. */
// Some computers only have up to 24 point
#define PI 3.141592654
#define BUTTON_TEXT_LEN 100
#define BUFSIZE 1000
#define ZOOM_FACTOR 1.6667 /* For zooming on the graphics */
#endif
#ifdef X11
/*********************************************
* X-Windows Specific Preprocessor Directives *
*********************************************/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>
/* Uncomment the line below if your X11 header files don't define XPointer */
/* typedef char *XPointer; */
#endif /* X11 Preprocessor Directives */
#ifdef WIN32
/*************************************************************
* Microsoft Windows (WIN32) Specific Preprocessor Directives *
*************************************************************/
#pragma warning(disable : 4996) // Turn off annoying warnings about strcmp.
#include <windows.h>
#include <WindowsX.h>
/* Using large values of pixel limits in order to preserve the slope of diagonal
* lines when their endpoints are clipped (one at a time) to these pixel limits.
* However, if these values are too large, then Windows will not draw the lines
* when zoomed way in. I have increased MAXPIXEL and MINPIXEL to these values so
* the diagonal lines do not change slope when zoomed way in. Also, I have tested
* to make sure these values are safe. Adding one more digit to these values
* may cause problems when zoomed way in.
* As a last note, specifying these values does not affect line clipping in X11.
* The most probable reason is that X11 does line clipping automatically.
* MW, June 2013.
*/
#define MAXPIXEL 21474836
#define MINPIXEL -21474836
/* Windows work in degrees, where as X11 and PostScript both work in radians.
* Thus, a conversion is needed for Windows.
*/
#define DEGTORAD(x) ((x)/180.*PI)
#endif /* Win32 preprocessor Directives */
/*************************************************************
* Common Structure Definitions *
*************************************************************/
/* Used to define where the output of drawscreen (graphics primitives in the user-controlled
* area) currently goes to: the screen or a postscript file.
*/
typedef enum {
SCREEN = 0,
POSTSCRIPT = 1
} t_display_type;
/* Indicates if this button displays text, a polygon or is just a separator.
*/
typedef enum {
BUTTON_TEXT = 0,
BUTTON_POLY,
BUTTON_SEPARATOR
} t_button_type;
/* Structure used to define the buttons on the right hand side of the main window (menu region).
* width, height: button size, in pixels.
* xleft, ytop: coordinates, in pixels, of the top-left corner of the button relative to its
* containing (menu) window.
* fcn: a callback function that is called when the button is pressed. This function takes one
* argument, a function pointer to the routine that can draw the graphics area (user routine).
* win, hwnd: X11 and Win32 data pointer to the window, respectively.
* button_type: indicates if this button displays text, a polygon or is just a separator.
* text: the text to display if this is a text button.
* poly: the polygon (up to 3 points right now) to display if this is a polygon button
* is_pressed: has the button been pressed, and is currently executing its callback?
* is_enabled: can you press this button right now? Visually will look "pushed in" when
* not enabled, and won't respond to clicks.
*/
typedef struct {
int width;
int height;
int xleft;
int ytop;
void (*fcn) (void (*drawscreen) (void));
#ifdef X11
Window win;
#else
HWND hwnd;
#endif
t_button_type type;
char text[BUTTON_TEXT_LEN];
int poly[3][2];
bool ispressed;
bool enabled;
} t_button;
/* Structure used to store all the buttons created in the menu region.
* button: array of pointers to all the buttons created [0..num_buttons-1]
* num_buttons: number of menu buttons created
*/
typedef struct {
t_button *button;
int num_buttons;
} t_button_state;
/* Structure used to store overall graphics state variables.
* initialized: true if the graphics window & state have been
* created and initialized, false otherwise.
* disp_type: Selects SCREEN or POSTSCRIPT
* background_cindex: index of the window (or page for PS) background colour
* currentcolor: current color in the graphics context
* currentlinestyle: current linestyle in the graphics context
* currentlinewidth: current linewidth in the graphics context
* currentfontsize: current font size in the graphics context
* current_draw_mode: select DRAW_NORMAL (for overwrite) or DRAW_XOR (for rubber-banding)
* ps: for PostScript output
* ProceedPressed: whether the Proceed button has been pressed
* statusMessage: user message to display
* font_is_loaded: whether a specified font size is loaded
* get_keypress_input: whether keypresses are sent back to callback functions
* get_mouse_move_input: whether mouse movements are sent back to callback functions
*/
typedef struct {
bool initialized;
t_display_type disp_type;
int background_cindex;
int currentcolor;
int currentlinestyle;
int currentlinewidth;
int currentfontsize;
e_draw_mode current_draw_mode;
FILE *ps;
bool ProceedPressed;
char statusMessage[BUFSIZE];
bool font_is_loaded[MAX_FONT_SIZE + 1];
bool get_keypress_input, get_mouse_move_input;
} t_gl_state;
/* Structure used to store coordinate information used for
* graphic transformations.
* display_width and display_height: entire screen size
* top_width and top_height: application window size in pixels
* init_xleft, init_xright, init_ytop, and init_ybot: initial world coordinates
* (for use in zoom_fit() to restore initial coordinates)
* xleft, xright, ytop, and ybot: boundaries of the graphic child window in world
* coordinates
* ps_left, ps_right, ps_top, and ps_bot: figure boundaries for PostScript output,
* in PostScript coordinates
* ps_xmult and ps_ymult: world to PostScript transformation factors (number of
* PostScript coordinates per world coordinate)
* wtos_xmult and wtos_ymult: world to screen transformation factors (number of
* screen pixels per world coordinate)
* stow_xmult and stow_ymult: screen to world transformation factors (number of
* world coordinates per screen pixel)
*/
typedef struct {
int display_width, display_height;
int top_width, top_height;
float init_xleft, init_xright, init_ytop, init_ybot;
float xleft, xright, ytop, ybot;
float ps_left, ps_right, ps_top, ps_bot;
float ps_xmult, ps_ymult;
float wtos_xmult, wtos_ymult;
float stow_xmult, stow_ymult;
} t_transform_coordinates;
/* Structure used to store state variables used for panning.
* previous_x and previous_y: (in window (pixel) coordinates,) last location of
* the cursor before new motion
* panning_enabled: whether panning is activated or de-activated
*/
typedef struct {
int previous_x, previous_y;
bool panning_enabled;
} t_panning_state;
#ifdef X11
/*************************************************************
* X11 Structure Definitions *
*************************************************************/
/* Structure used to store X Windows state variables.
* display: Structure containing information needed to
* communicate with Xlib.
* screen_num: Value the Xlib server uses to identify every
* connected screen.
* colors: Color indices paased back from X Windows.
* private_cmap: "None" unless a private cmap was allocated.
* toplevel, menu, textarea: Toplevel window and 2 child windows.
* gc_normal, gc_xor, current_gc: Graphic contexts for drawing
* in the graphics area. (gc_normal for overwrite
* drawing, gc_xor for rubber band drawing, and
* current_gc is the current gc used)
* gc_menus: Graphic context for drawing in the status message
* and menu area
* font_info: Data for each font size.
*/
typedef struct {
Display *display;
int screen_num;
int colors[NUM_COLOR];
Colormap private_cmap;
Window toplevel, menu, textarea;
GC gc_normal, gc_xor, gc_menus, current_gc;
XFontStruct *font_info[MAX_FONT_SIZE+1];
} t_x11_state;
#endif
#ifdef WIN32
/*************************************************************
* WIN32 Structure Definitions *
*************************************************************/
/* Flag used for the "window" button. Before the user presses the button, the
* "window" operation is in WINDOW_DEACTIVATED state. After user presses the
* button, the operation proceeds to WAITING_FOR_FIRST_CORNER_POINT state and
* waits for the user to click the first point in the graphics area as the first
* corner for rubber band drawing. After user clicks the first point, the operation
* proceeds to WAITING_FOR_SECOND_CORNER_POINT and waits for the user to click on
* the second point to define the rectangular region enclosed by the rubber band.
* Then the application window will zoom in to the region defined and the "window"
* operation goes back to WINDOW_DEACTIVATED.
*/
typedef enum {
WINDOW_DEACTIVATED = 0,
WAITING_FOR_FIRST_CORNER_POINT,
WAITING_FOR_SECOND_CORNER_POINT
} t_window_button_state;
/* Structure used to store Win32 state variables.
* InEventLoop: Whether in event_loop(); used to indicate if part of the application window need
* to be redrawn when system makes request by sending WM_PAINT message in the
* WIN32_GraphicsWND callback function.
* windowAdjustFlag: Flag used for the "Window" button operation. This variable has 3 states
* which are defined above.
* adjustButton: Holds the index of the "Window" button in the array of buttons created
* adjustRect: Used for the "Window" button operation. Holds the boundary coordinates (in screen
* pixels) of the region enclosed by the rubber band.
* hMainWnd, hGraphicsWnd, hButtonsWnd, hStatusWnd: Handles to the top level window and
* 3 subwindows.
* hGraphicsDC: Handle to the graphics device context.
* hGraphicsPen, hGraphicsBrush, hGrayBrush, hGraphicsFont: Handles to Windows GDI objects used
* for drawing. (hGraphicsPen for drawing lines, hGraphicsBrush for filling shapes,
* and hGrayBrush for filling the background of the status message and menu areas)
* font_info: Data for each font size.
*/
typedef struct {
bool InEventLoop;
t_window_button_state windowAdjustFlag;
int adjustButton;
RECT adjustRect;
HWND hMainWnd, hGraphicsWnd, hButtonsWnd, hStatusWnd;
HDC hGraphicsDC;
HPEN hGraphicsPen;
HBRUSH hGraphicsBrush, hGrayBrush;
HFONT hGraphicsFont;
LOGFONT *font_info[MAX_FONT_SIZE+1];
} t_win32_state;
#endif
/*********************************************************************
* File scope variables. *
*********************************************************************/
// Need to initialize graphics_loaded to false, since checking it is
// how we avoid multiple construction or destruction of the graphics
// window. Initializing display type and background color index is not
// necessary since they are set in init_graphics(), but doing so
// just for safety.
static t_gl_state gl_state = {false, SCREEN, 0};
// Stores all the menu buttons created. Initialize the button pointer
// and num_buttons for safety.
static t_button_state button_state = {NULL, 0};
// Contains all coordinates useful for graphics transformation
static t_transform_coordinates trans_coord;
// Initialize panning_enabled to false, so panning is not activated
static t_panning_state pan_state = {0, 0, false};
// Color references
static const char *ps_cnames[NUM_COLOR] = {"white", "black", "grey55", "grey75",
"blue", "green", "yellow", "cyan", "red", "pink", "lightpink", "darkgreen",
"magenta", "bisque", "lightskyblue", "thistle", "plum", "khaki", "coral",
"turquoise", "mediumpurple", "darkslateblue", "darkkhaki"};
#ifdef X11
/*************************************************
* X-Windows Specific File-scope Variables *
**************************************************/
// Stores all state variables for X Windows
t_x11_state x11_state;
#endif /* X11 file scope variables */
#ifdef WIN32
/*****************************************************
* Microsoft Windows (Win32) File Scope Variables *
*****************************************************/
/* Linestyle references for Win32. */
static const int win32_line_styles[2] = { PS_SOLID, PS_DASH };
/* Color references for Win32. Colors have to be specified by RGB values for Win32. */
static const COLORREF win32_colors[NUM_COLOR] = { RGB(255, 255, 255),
RGB(0, 0, 0), RGB(128, 128, 128), RGB(192, 192, 192), RGB(0, 0, 255),
RGB(0, 255, 0), RGB(255, 255, 0), RGB(0, 255, 255), RGB(255, 0, 0), RGB(255, 192, 203),
RGB(255, 182, 193), RGB(0, 128, 0), RGB(255, 0, 255), RGB(255, 228, 196), RGB(135, 206, 250),
RGB(216, 191, 216), RGB(221, 160, 221), RGB(240, 230, 140), RGB(255, 127, 80),
RGB(64, 224, 208), RGB(147, 112, 219), RGB(72, 61, 139), RGB(189, 183, 107)};
/* Name of each window */
static TCHAR szAppName[256], szGraphicsName[] = TEXT("VPR Graphics"),
szStatusName[] = TEXT("VPR Status"), szButtonsName[] = TEXT("VPR Buttons");
/* Stores all state variables for Win32 */
static t_win32_state win32_state = {false, WINDOW_DEACTIVATED, -1};
#endif /* WIN32 file scope variables */
/*********************************************
* Common Subroutine Declarations *
*********************************************/
static void *my_malloc(int ibytes);
static void *my_realloc(void *memblk, int ibytes);
/* translation from screen coordinates to the world coordinates *
* used by the client program. */
static float xscrn_to_world(int x);
static float yscrn_to_world(int y);
/* translation from world to screen coordinates */
static int xworld_to_scrn (float worldx);
static int yworld_to_scrn (float worldy);
/* translation from world to PostScript coordinates */
static float xworld_to_post(float worldx);
static float yworld_to_post(float worldy);
static void force_setcolor(int cindex);
static void force_setlinestyle(int linestyle);
static void force_setlinewidth(int linewidth);
static void force_setfontsize (int pointsize);
static void load_font(int pointsize);
static void reset_common_state ();
static void build_default_menu (void);
/* Function declarations for button responses */
static void translate_up (void (*drawscreen) (void));
static void translate_left (void (*drawscreen) (void));
static void translate_right (void (*drawscreen) (void));
static void translate_down (void (*drawscreen) (void));
static void panning_execute (int x, int y, void (*drawscreen) (void));
static void panning_on (int start_x, int start_y);
static void panning_off (void);
static void zoom_in (void (*drawscreen) (void));
static void zoom_out (void (*drawscreen) (void));
static void handle_zoom_in (float x, float y, void (*drawscreen) (void));
static void handle_zoom_out (float x, float y, void (*drawscreen) (void));
static void zoom_fit (void (*drawscreen) (void));
static void adjustwin (void (*drawscreen) (void));
static void postscript (void (*drawscreen) (void));
static void proceed (void (*drawscreen) (void));
static void quit (void (*drawscreen) (void));
static void map_button (int bnum);
static void unmap_button (int bnum);
#ifdef X11
/****************************************************
* X-Windows Specific Subroutine Declarations *
*****************************************************/
/* Helper functions for X11; not visible to client program. */
static void x11_init_graphics (const char* window_name, int cindex);
static void x11_event_loop (void (*act_on_mousebutton)
(float x, float y, t_event_buttonPressed button_info),
void (*act_on_mousemove)(float x, float y),
void (*act_on_keypress)(char key_pressed),
void (*drawscreen) (void));
static Bool x11_test_if_exposed (Display *disp, XEvent *event_ptr,
XPointer dummy);
static void x11_build_textarea (void);
static void x11_drawbut (int bnum);
static int x11_which_button (Window win);
static void x11_turn_on_off (int pressed);
static void x11_drawmenu(void);
static void x11_handle_expose (XEvent report, void (*drawscreen) (void));
static void x11_handle_configure_notify (XEvent report);
static void x11_handle_button_info (t_event_buttonPressed *button_info,
int buttonNumber, int Xbutton_state);
#endif /* X11 Declarations */
#ifdef WIN32
/*******************************************************************
* Win32-specific subroutine declarations *
*******************************************************************/
/* Helper function called by init_graphics(). Not visible to client program. */
static void win32_init_graphics (const char* window_name, int cindex);
/* Callback functions for the top-level window and 3 sub-windows.
* Windows uses an odd mix of events and callbacks, so it needs these.
*/
static LRESULT CALLBACK WIN32_GraphicsWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WIN32_StatusWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WIN32_ButtonsWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK WIN32_MainWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
// For Win32, need to save pointers to these callback functions at file
// scope, since windows has a bizarre event loop structure where you poll
// for events, but then dispatch the event and get called via a callback
// from windows (WIN32_GraphicsWND, below). I can't figure out why windows
// does things this way, but it is what makes saving these function pointers
// necessary. VB.
static void (*win32_mouseclick_ptr)(float x, float y, t_event_buttonPressed button_info);
static void (*win32_mousemove_ptr)(float x, float y);
static void (*win32_keypress_ptr)(char entered_char);
static void (*win32_drawscreen_ptr)(void);
// Helper functions for WIN32_GraphicsWND callback function
static void win32_GraphicsWND_handle_WM_PAINT(HWND hwnd, PAINTSTRUCT &ps, HPEN &hDotPen,
RECT &oldAdjustRect);
static void win32_GraphicsWND_handle_WM_LRBUTTONDOWN(UINT message, WPARAM wParam, LPARAM lParam,
int &X, int &Y, RECT &oldAdjustRect);
static void win32_GraphicsWND_handle_WM_MBUTTONDOWN(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam);
static void win32_GraphicsWND_handle_WM_MOUSEMOVE(LPARAM lParam, int &X, int &Y,
RECT &oldAdjustRect);
// Functions for displaying errors in a message box on windows.
static void WIN32_SELECT_ERROR();
static void WIN32_DELETE_ERROR();
static void WIN32_CREATE_ERROR();
static void WIN32_DRAW_ERROR();
static void win32_invalidate_screen();
static void win32_reset_state ();
static void win32_drain_message_queue ();
static void win32_handle_mousewheel_zooming(WPARAM wParam, LPARAM lParam);
static void win32_handle_button_info (t_event_buttonPressed &button_info, UINT message,
WPARAM wParam);
#endif /* Win32 Declarations */
/*********************************************************
* Common Subroutine Definitions *
*********************************************************/
/* safer malloc */
static void *my_malloc(int ibytes) {
void *mem;
mem = (void*)malloc(ibytes);
if (mem == NULL) {
printf("memory allocation failed!");
exit(-1);
}
return mem;
}
/* safer realloc */
static void *my_realloc(void *memblk, int ibytes) {
void *mem;
mem = (void*)realloc(memblk, ibytes);
if (mem == NULL) {
printf("memory allocation failed!");
exit(-1);
}
return mem;
}
/* translation from screen coordinates to the world coordinates *
* in the x direction. */
static float xscrn_to_world(int x)
{
float world_coord_x;
world_coord_x = ((float) x)*trans_coord.stow_xmult + trans_coord.xleft;
return world_coord_x;
}
/* translation from screen coordinates to the world coordinates *
* in the y direction. */
static float yscrn_to_world(int y)
{
float world_coord_y;
world_coord_y = ((float) y)*trans_coord.stow_ymult + trans_coord.ytop;
return world_coord_y;
}
/* Translates from world (client program) coordinates to screen coordinates
* (pixels) in the x direction. Add 0.5 at end for extra half-pixel accuracy.
*/
static int xworld_to_scrn (float worldx)
{
int winx;
winx = (int) ((worldx-trans_coord.xleft)*trans_coord.wtos_xmult + 0.5);
#ifdef WIN32
/* Avoids overflow in the Window routines. This will allow horizontal *
* and vertical lines to be drawn correctly regardless of zooming, but *
* will cause diagonal lines that go way off screen to change their *
* slope as you zoom in. The only way I can think of to completely fix *
* this problem is to do all the clipping in advance in floating point, *
* then convert to integers and call Windows. This is a lot of extra *
* coding, and means that coordinates will be clipped twice, even though *
* this "Super Zoom" problem won't occur unless users zoom way in on *
* the graphics. */
winx = max (winx, MINPIXEL);
winx = min (winx, MAXPIXEL);
#endif
return (winx);
}
/* Translates from world (client program) coordinates to screen coordinates
* (pixels) in the y direction. Add 0.5 at end for extra half-pixel accuracy.
*/
static int yworld_to_scrn (float worldy)
{
int winy;
winy = (int) ((worldy-trans_coord.ytop)*trans_coord.wtos_ymult + 0.5);
#ifdef WIN32
/* Avoid overflow in the X/Win32 Window routines. */
winy = max (winy, MINPIXEL);
winy = min (winy, MAXPIXEL);
#endif
return (winy);
}
/* translation from world to PostScript coordinates in the x direction */
static float xworld_to_post(float worldx)
{
float ps_coord_x;
ps_coord_x = ((worldx)-trans_coord.xleft)*trans_coord.ps_xmult + trans_coord.ps_left;
return ps_coord_x;
}
/* translation from world to PostScript coordinates in the y direction */
static float yworld_to_post(float worldy)
{
float ps_coord_y;
ps_coord_y = ((worldy)-trans_coord.ybot)*trans_coord.ps_ymult + trans_coord.ps_bot;
return ps_coord_y;
}
/* Sets the current graphics context colour to cindex, regardless of whether we think it is
* needed or not.
*/
static void force_setcolor (int cindex)
{
gl_state.currentcolor = cindex;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
XSetForeground (x11_state.display, x11_state.current_gc, x11_state.colors[cindex]);
#else /* Win32 */
int win_linestyle, linewidth;
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = win32_colors[cindex];
lb.lbHatch = (LONG)NULL;
win_linestyle = win32_line_styles[gl_state.currentlinestyle];
linewidth = max (gl_state.currentlinewidth, 1); // Win32 won't draw 0 width dashed lines.
/* Delete previously used pen */
if(!DeleteObject(win32_state.hGraphicsPen))
WIN32_DELETE_ERROR();
/* Create a new pen */
win32_state.hGraphicsPen = ExtCreatePen(PS_GEOMETRIC | win_linestyle |
PS_ENDCAP_FLAT, linewidth, &lb, (LONG)NULL, NULL);
if(!win32_state.hGraphicsPen)
WIN32_CREATE_ERROR();
/* Select created pen into specified device context */
if(!SelectObject(win32_state.hGraphicsDC, win32_state.hGraphicsPen))
WIN32_SELECT_ERROR();
/* Delete previously used brush */
if(!DeleteObject(win32_state.hGraphicsBrush))
WIN32_DELETE_ERROR();
/* Create a new brush */
win32_state.hGraphicsBrush = CreateSolidBrush(win32_colors[gl_state.currentcolor]);
if(!win32_state.hGraphicsBrush)
WIN32_CREATE_ERROR();
/* Select created brush into specified device context */
if(!SelectObject(win32_state.hGraphicsDC, win32_state.hGraphicsBrush))
WIN32_SELECT_ERROR();
#endif
}
else {
fprintf (gl_state.ps,"%s\n", ps_cnames[cindex]);
}
}
/* Sets the current graphics context colour to cindex if it differs from the old colour */
void setcolor (int cindex)
{
if (gl_state.currentcolor != cindex)
force_setcolor (cindex);
}
/* Sets the current graphics context color to the index that corresponds to the
* string name passed in. Slower, but maybe more convenient for simple
* client code.
*/
void setcolor_by_name (string cname) {
int icolor = -1;
for (int i = 0; i < NUM_COLOR; i++) {
if (cname == ps_cnames[i]) {
icolor = i;
break;
}
}
if (icolor == -1) {
cout << "Error: unknown color " << cname << endl;
}
else {
setcolor (icolor);
}
}
int getcolor() {
return gl_state.currentcolor;
}
/* Sets the current linestyle to linestyle in the graphics context.
* Note SOLID is 0 and DASHED is 1 for linestyle.
*/
static void force_setlinestyle (int linestyle)
{
gl_state.currentlinestyle = linestyle;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
static int x_vals[2] = {LineSolid, LineOnOffDash};
XSetLineAttributes (x11_state.display, x11_state.current_gc, gl_state.currentlinewidth,
x_vals[linestyle], CapButt, JoinMiter);
#else // Win32
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = win32_colors[gl_state.currentcolor];
lb.lbHatch = (LONG)NULL;
int win_linestyle = win32_line_styles[linestyle];
/* Win32 won't draw 0 width dashed lines. */
int linewidth = max (gl_state.currentlinewidth, 1);
/* Delete previously used pen */
if(!DeleteObject(win32_state.hGraphicsPen))
WIN32_DELETE_ERROR();
/* Create a new pen */
win32_state.hGraphicsPen = ExtCreatePen(PS_GEOMETRIC | win_linestyle |
PS_ENDCAP_FLAT, linewidth, &lb, (LONG)NULL, NULL);
if(!win32_state.hGraphicsPen)
WIN32_CREATE_ERROR();
/* Select created pen into specified device context */
if(!SelectObject(win32_state.hGraphicsDC, win32_state.hGraphicsPen))
WIN32_SELECT_ERROR();
#endif
}
else {
if (linestyle == SOLID)
fprintf (gl_state.ps,"linesolid\n");
else if (linestyle == DASHED)
fprintf (gl_state.ps, "linedashed\n");
else {
printf ("Error: invalid linestyle: %d\n", linestyle);
exit (1);
}
}
}
/* Change the linestyle in the graphics context only if it differs from the current
* linestyle.
*/
void setlinestyle (int linestyle)
{
if (linestyle != gl_state.currentlinestyle)
force_setlinestyle (linestyle);
}
/* Sets current linewidth in the graphics context.
* linewidth should be greater than or equal to 0 to make any sense.
*/
static void force_setlinewidth (int linewidth)
{
gl_state.currentlinewidth = linewidth;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
static int x_vals[2] = {LineSolid, LineOnOffDash};
XSetLineAttributes (x11_state.display, x11_state.current_gc, linewidth,
x_vals[gl_state.currentlinestyle], CapButt, JoinMiter);
#else /* Win32 */
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = win32_colors[gl_state.currentcolor];
lb.lbHatch = (LONG)NULL;
int win_linestyle = win32_line_styles[gl_state.currentlinestyle];
if (linewidth == 0)
linewidth = 1; // Win32 won't draw dashed 0 width lines.
/* Delete previously used pen */
if(!DeleteObject(win32_state.hGraphicsPen))
WIN32_DELETE_ERROR();
/* Create a new pen */
win32_state.hGraphicsPen = ExtCreatePen(PS_GEOMETRIC | win_linestyle |
PS_ENDCAP_FLAT, linewidth, &lb, (LONG)NULL, NULL);
if(!win32_state.hGraphicsPen)
WIN32_CREATE_ERROR();
/* Select created pen into specified device context */
if(!SelectObject(win32_state.hGraphicsDC, win32_state.hGraphicsPen))
WIN32_SELECT_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"%d setlinewidth\n", linewidth);
}
}
/* Sets the linewidth in the grahpics context, if it differs from the current value.
*/
void setlinewidth (int linewidth)
{
if (linewidth != gl_state.currentlinewidth)
force_setlinewidth (linewidth);
}
/* Force the selected fontsize to be applied to the graphics context,
* whether or not it appears to match the current fontsize. This is necessary
* when switching between postscript and window out, for example.
* Valid point sizes are between 1 and MAX_FONT_SIZE.
*/
static void force_setfontsize (int pointsize)
{
if (pointsize < 1)
pointsize = 1;
if (pointsize > MAX_FONT_SIZE)
pointsize = MAX_FONT_SIZE;
gl_state.currentfontsize = pointsize;
if (gl_state.disp_type == SCREEN) {
load_font (pointsize);
#ifdef X11
XSetFont(x11_state.display, x11_state.current_gc, x11_state.font_info[pointsize]->fid);
#else /* Win32 */
/* Delete previously used font */
if(!DeleteObject(win32_state.hGraphicsFont))
WIN32_DELETE_ERROR();
/* Create a new font */
win32_state.hGraphicsFont = CreateFontIndirect(win32_state.font_info[pointsize]);
if(!win32_state.hGraphicsFont)
WIN32_CREATE_ERROR();
/* Select created font into specified device context */
if(!SelectObject(win32_state.hGraphicsDC, win32_state.hGraphicsFont) )
WIN32_SELECT_ERROR();
#endif
}
else {
/* PostScript: set up font and centering function */
fprintf(gl_state.ps,"%d setfontsize\n",pointsize);
}
}
/* For efficiency, this routine doesn't do anything if no change is
* implied. If you want to force the graphics context or PS file
* to have font info set, call force_setfontsize (this is necessary
* in initialization and screen / Postscript switches).
*/
void setfontsize (int pointsize)
{
if (pointsize != gl_state.currentfontsize)
force_setfontsize (pointsize);
}
/* Puts a triangle in the poly array for button[bnum]. Haven't made this work for
* win32 yet and instead put "U", "D" excetra on the arrow buttons.
* VB To-do: make work for win32 someday.
*/
#ifdef X11
static void setpoly (int bnum, int xc, int yc, int r, float theta)
{
int i;
button_state.button[bnum].type = BUTTON_POLY;
for (i=0;i<3;i++) {
button_state.button[bnum].poly[i][0] = (int) (xc + r*cos(theta) + 0.5);
button_state.button[bnum].poly[i][1] = (int) (yc + r*sin(theta) + 0.5);
theta += (float)(2*PI/3);
}
}
#endif // X11
/* Maps a button onto the screen and set it up for input, etc. */
static void map_button (int bnum)
{
button_state.button[bnum].ispressed = 0;
if (button_state.button[bnum].type != BUTTON_SEPARATOR) {
#ifdef X11
button_state.button[bnum].win = XCreateSimpleWindow(x11_state.display,x11_state.menu,
button_state.button[bnum].xleft,
button_state.button[bnum].ytop,
button_state.button[bnum].width,
button_state.button[bnum].height, 0,
x11_state.colors[WHITE],
x11_state.colors[LIGHTGREY]);
XMapWindow (x11_state.display, button_state.button[bnum].win);
XSelectInput (x11_state.display, button_state.button[bnum].win, ButtonPressMask);
#else
button_state.button[bnum].hwnd = CreateWindow(TEXT("button"),
TEXT(button_state.button[bnum].text),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
button_state.button[bnum].xleft,
button_state.button[bnum].ytop,
button_state.button[bnum].width,
button_state.button[bnum].height,
win32_state.hButtonsWnd, (HMENU)(200+bnum),
(HINSTANCE) GetWindowLong(win32_state.hMainWnd,
GWL_HINSTANCE),
NULL);
if(!InvalidateRect(win32_state.hButtonsWnd, NULL, TRUE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hButtonsWnd))
WIN32_DRAW_ERROR();
#endif
}
else { // Separator, not a button.
#ifdef X11
button_state.button[bnum].win = -1;
#else // WIN32
button_state.button[bnum].hwnd = NULL;
if(!InvalidateRect(win32_state.hButtonsWnd, NULL, TRUE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hButtonsWnd))
WIN32_DRAW_ERROR();
#endif
}
}
static void unmap_button (int bnum)
{
/* Unmaps (removes) a button from the screen. */
if (button_state.button[bnum].type != BUTTON_SEPARATOR) {
#ifdef X11
XUnmapWindow (x11_state.display, button_state.button[bnum].win);
#else
if(!DestroyWindow(button_state.button[bnum].hwnd))
WIN32_DRAW_ERROR();
if(!InvalidateRect(win32_state.hButtonsWnd, NULL, TRUE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hButtonsWnd))
WIN32_DRAW_ERROR();
#endif
}
}
/* Creates a new button below the button containing prev_button_text. *
* The text and button function are set according to button_text and *
* button_func, respectively. */
void create_button (const char *prev_button_text , const char *button_text,
void (*button_func) (void (*drawscreen) (void)))
{
int i, bnum, space, bheight;
t_button_type button_type = BUTTON_TEXT;
space = 8;
/* Only allow new buttons that are text or separator (not poly) types.
* They can also only go after buttons that are text buttons.
*/
bnum = -1;
for (i=0; i < button_state.num_buttons;i++) {
if (button_state.button[i].type == BUTTON_TEXT &&
strcmp (button_state.button[i].text, prev_button_text) == 0) {
bnum = i + 1;
break;
}
}
if (bnum == -1) {
printf ("Error in create_button: button with text %s not found.\n",
prev_button_text);
exit (1);
}
button_state.button = (t_button *) my_realloc (button_state.button,
(button_state.num_buttons+1) * sizeof (t_button));
/* NB: Requirement that you specify the button that this button goes under *
* guarantees that button[num_buttons-2] exists and is a text button. */
/* Special string to make a separator. */
if (!strncmp(button_text, "---", 3)) {
bheight = 2;
button_type = BUTTON_SEPARATOR;
}
else
bheight = 26;
for (i=button_state.num_buttons;i>bnum;i--) {
button_state.button[i].xleft = button_state.button[i-1].xleft;
button_state.button[i].ytop = button_state.button[i-1].ytop + bheight + space;
button_state.button[i].height = button_state.button[i-1].height;
button_state.button[i].width = button_state.button[i-1].width;
button_state.button[i].type = button_state.button[i-1].type;
strcpy (button_state.button[i].text, button_state.button[i-1].text);
button_state.button[i].fcn = button_state.button[i-1].fcn;
button_state.button[i].ispressed = button_state.button[i-1].ispressed;
button_state.button[i].enabled = button_state.button[i-1].enabled;
unmap_button (i-1);
}
i = bnum;
button_state.button[i].xleft = 6;
button_state.button[i].ytop = button_state.button[i-1].ytop + button_state.button[i-1].height
+ space;
button_state.button[i].height = bheight;
button_state.button[i].width = 90;
button_state.button[i].type = button_type;
strncpy (button_state.button[i].text, button_text, BUTTON_TEXT_LEN);
button_state.button[i].fcn = button_func;
button_state.button[i].ispressed = false;
button_state.button[i].enabled = true;
button_state.num_buttons++;
for (i = 0; i<button_state.num_buttons;i++)
map_button (i);
}
/* Destroys the button with text button_text. */
void
destroy_button (const char *button_text)
{
int i, bnum, space, bheight;
bnum = -1;
space = 8;
for (i = 0; i < button_state.num_buttons; i++) {
if (button_state.button[i].type == BUTTON_TEXT &&
strcmp (button_state.button[i].text, button_text) == 0) {
bnum = i;
break;
}
}
if (bnum == -1) {
printf ("Error in destroy_button: button with text %s not found.\n",
button_text);
exit (1);
}
bheight = button_state.button[bnum].height;
for (i=bnum+1;i<button_state.num_buttons;i++) {
button_state.button[i-1].xleft = button_state.button[i].xleft;
button_state.button[i-1].ytop = button_state.button[i].ytop - bheight - space;
button_state.button[i-1].height = button_state.button[i].height;
button_state.button[i-1].width = button_state.button[i].width;
button_state.button[i-1].type = button_state.button[i].type;
strcpy (button_state.button[i-1].text, button_state.button[i].text);
button_state.button[i-1].fcn = button_state.button[i].fcn;
button_state.button[i-1].ispressed = button_state.button[i].ispressed;
button_state.button[i-1].enabled = button_state.button[i].enabled;
unmap_button (i);
}
unmap_button(bnum);
button_state.button = (t_button *) my_realloc (button_state.button,
(button_state.num_buttons-1) * sizeof (t_button));
button_state.num_buttons--;
for (i=bnum; i<button_state.num_buttons;i++)
map_button (i);
}
/* Open the toplevel window, get the colors, 2 graphics *
* contexts, load a font, and set up the toplevel window *
* Calls build_default_menu to set up the default menu. */
void
init_graphics (const char *window_name, int cindex)
{
if (gl_state.initialized) // Singleton graphics.
return;
reset_common_state ();
gl_state.disp_type = SCREEN;
gl_state.background_cindex = cindex;
#ifdef X11
x11_init_graphics (window_name, cindex);
#else /* WIN32 */
win32_init_graphics (window_name, cindex);
#endif
gl_state.initialized = true;
}
static void reset_common_state () {
gl_state.currentcolor = BLACK;
gl_state.currentlinestyle = SOLID;
gl_state.currentlinewidth = 0;
gl_state.currentfontsize = 12;
gl_state.current_draw_mode = DRAW_NORMAL;
for (int i=0;i<=MAX_FONT_SIZE;i++)
gl_state.font_is_loaded[i] = false; /* No fonts loaded yet. */
gl_state.ProceedPressed = false;
gl_state.get_keypress_input = false;
gl_state.get_mouse_move_input = false;
#ifdef WIN32
win32_reset_state ();
#endif
}
static void
update_transform (void)
{
/* Set up the factors for transforming from the user world to X/Win32 screen
* (pixel) coordinates. */
float mult, diff, y1, y2, x1, x2;
/* X Window coordinates go from (0,0) to (width-1,height-1) */
diff = trans_coord.xright - trans_coord.xleft;
trans_coord.wtos_xmult = (trans_coord.top_width - 1 - MWIDTH) / diff;
diff = trans_coord.ybot - trans_coord.ytop;
trans_coord.wtos_ymult = (trans_coord.top_height - 1 - T_AREA_HEIGHT) / diff;
/* Need to use same scaling factor to preserve aspect ratio */
if (fabs(trans_coord.wtos_xmult) <= fabs(trans_coord.wtos_ymult)) {
mult = (float)(fabs(trans_coord.wtos_ymult/trans_coord.wtos_xmult));
y1 = trans_coord.ytop - (trans_coord.ybot-trans_coord.ytop)*(mult-1)/2;
y2 = trans_coord.ybot + (trans_coord.ybot-trans_coord.ytop)*(mult-1)/2;
trans_coord.ytop = y1;
trans_coord.ybot = y2;
}
else {
mult = (float)(fabs(trans_coord.wtos_xmult/trans_coord.wtos_ymult));
x1 = trans_coord.xleft - (trans_coord.xright-trans_coord.xleft)*(mult-1)/2;
x2 = trans_coord.xright + (trans_coord.xright-trans_coord.xleft)*(mult-1)/2;
trans_coord.xleft = x1;
trans_coord.xright = x2;
}
diff = trans_coord.xright - trans_coord.xleft;
trans_coord.wtos_xmult = (trans_coord.top_width - 1 - MWIDTH) / diff;
diff = trans_coord.ybot - trans_coord.ytop;
trans_coord.wtos_ymult = (trans_coord.top_height - 1 - T_AREA_HEIGHT) / diff;
trans_coord.stow_xmult = 1/trans_coord.wtos_xmult;
trans_coord.stow_ymult = 1/trans_coord.wtos_ymult;
}
static void
update_ps_transform (void)
{
/* Postscript coordinates start at (0,0) for the lower left hand corner *
* of the page and increase upwards and to the right. For 8.5 x 11 *
* sheet, coordinates go from (0,0) to (612,792). Spacing is 1/72 inch.*
* I'm leaving a minimum of half an inch (36 units) of border around *
* each edge. */
float mult, ps_width, ps_height;
ps_width = 540.; /* 72 * 7.5 */
ps_height = 720.; /* 72 * 10 */
trans_coord.ps_xmult = ps_width / (trans_coord.xright - trans_coord.xleft);
trans_coord.ps_ymult = ps_height / (trans_coord.ytop - trans_coord.ybot);
/* Need to use same scaling factor to preserve aspect ratio. *
* I show exactly as much on paper as the screen window shows, *
* or the user specifies. */
if (fabs(trans_coord.ps_xmult) <= fabs(trans_coord.ps_ymult)) {
trans_coord.ps_left = 36.;
trans_coord.ps_right = (float)(36. + ps_width);
mult = fabs(trans_coord.ps_xmult * (trans_coord.ytop - trans_coord.ybot));
trans_coord.ps_bot = (float)(396. - mult/2);
mult = fabs(trans_coord.ps_xmult * (trans_coord.ytop - trans_coord.ybot));
trans_coord.ps_top = (float)(396. + mult/2);
/* Maintain aspect ratio but watch signs */
trans_coord.ps_ymult = (trans_coord.ps_xmult*trans_coord.ps_ymult < 0) ?
-trans_coord.ps_xmult : trans_coord.ps_xmult;
}
else {
trans_coord.ps_bot = 36.;
trans_coord.ps_top = (float)(36. + ps_height);
mult = fabs(trans_coord.ps_ymult * (trans_coord.xright - trans_coord.xleft));
trans_coord.ps_left = (float)(306. - mult/2);
mult = fabs(trans_coord.ps_ymult * (trans_coord.xright - trans_coord.xleft));
trans_coord.ps_right = (float)(306. + mult/2);
/* Maintain aspect ratio but watch signs */
trans_coord.ps_xmult = (trans_coord.ps_xmult*trans_coord.ps_ymult < 0) ?
-trans_coord.ps_ymult : trans_coord.ps_ymult;
}
}
/* The program's main event loop. Must be passed a user routine
* drawscreen which redraws the screen. It handles all window resizing
* zooming etc. itself. If the user clicks a mousebutton in the graphics
* (toplevel) area, the act_on_mousebutton routine passed in is called.
*/
void
event_loop (void (*act_on_mousebutton)(float x, float y, t_event_buttonPressed button_info),
void (*act_on_mousemove)(float x, float y),
void (*act_on_keypress)(char key_pressed),
void (*drawscreen) (void))
{
#ifdef X11
x11_event_loop(act_on_mousebutton, act_on_mousemove, act_on_keypress, drawscreen);
#else /* Win32 */
// For event handling with Win32, need to set these file scope function
// pointers, then dispatch the event and get called via callbacks from
// Windows (eg. WIN32_GraphicsWND()). Actual event handling is done
// in these Win32-specific callback functions.
MSG msg;
win32_mouseclick_ptr = act_on_mousebutton;
win32_mousemove_ptr = act_on_mousemove;
win32_keypress_ptr = act_on_keypress;
win32_drawscreen_ptr = drawscreen;
gl_state.ProceedPressed = false;
win32_state.InEventLoop = true;
win32_invalidate_screen();
while(!gl_state.ProceedPressed && GetMessage(&msg, NULL, 0, 0)) {
//TranslateMessage(&msg);
if (msg.message == WM_CHAR) { // only the top window can get keyboard events
msg.hwnd = win32_state.hMainWnd;
}
DispatchMessage(&msg);
}
win32_state.InEventLoop = false;
#endif
}
void
clearscreen (void)
{
int savecolor;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
XClearWindow (x11_state.display, x11_state.toplevel);
#else /* Win32 */
savecolor = gl_state.currentcolor;
setcolor(gl_state.background_cindex);
fillrect (trans_coord.xleft, trans_coord.ytop,
trans_coord.xright, trans_coord.ybot);
setcolor(savecolor);
#endif
}
else { // Postscript
/* erases current page. Don't use erasepage, since this will erase *
* everything, (even stuff outside the clipping path) causing *
* problems if this picture is incorporated into a larger document. */
savecolor = gl_state.currentcolor;
setcolor (gl_state.background_cindex);
fprintf(gl_state.ps,"clippath fill\n\n");
setcolor (savecolor);
}
}
/* Return 1 if I can quarantee no part of this rectangle will *
* lie within the user drawing area. Otherwise return 0. *
* Note: this routine is only used to help speed (and to shrink ps *
* files) -- it will be highly effective when the graphics are zoomed *
* in and lots are off-screen. I don't have to pre-clip for *
* correctness. */
static int
rect_off_screen (float x1, float y1, float x2, float y2)
{
float xmin, xmax, ymin, ymax;
xmin = min (trans_coord.xleft, trans_coord.xright);
if (x1 < xmin && x2 < xmin)
return (1);
xmax = max (trans_coord.xleft, trans_coord.xright);
if (x1 > xmax && x2 > xmax)
return (1);
ymin = min (trans_coord.ytop, trans_coord.ybot);
if (y1 < ymin && y2 < ymin)
return (1);
ymax = max (trans_coord.ytop, trans_coord.ybot);
if (y1 > ymax && y2 > ymax)
return (1);
return (0);
}
void
drawline (float x1, float y1, float x2, float y2)
{
/* Draw a line from (x1,y1) to (x2,y2) in the user-drawable area. *
* Coordinates are in world (user) space. */
/* Pre-clipping has been tested on both Windows and Linux, and it was found to be useful *
* for speeding up drawscreen() runtime. */
if (rect_off_screen(x1,y1,x2,y2))
return;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
/* Xlib.h prototype has x2 and y1 mixed up. */
XDrawLine(x11_state.display, x11_state.toplevel, x11_state.current_gc, xworld_to_scrn(x1),
yworld_to_scrn(y1), xworld_to_scrn(x2), yworld_to_scrn(y2));
#else /* Win32 */
if (!BeginPath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
if(!MoveToEx (win32_state.hGraphicsDC, xworld_to_scrn(x1), yworld_to_scrn(y1), NULL))
WIN32_DRAW_ERROR();
if(!LineTo (win32_state.hGraphicsDC, xworld_to_scrn(x2), yworld_to_scrn(y2)))
WIN32_DRAW_ERROR();
if (!EndPath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
if (!StrokePath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"%.2f %.2f %.2f %.2f drawline\n",xworld_to_post(x1),yworld_to_post(y1),
xworld_to_post(x2),yworld_to_post(y2));
}
}
/* (x1,y1) and (x2,y2) are diagonally opposed corners, in world coords. */
void
drawrect (float x1, float y1, float x2, float y2)
{
int xw1, yw1, xw2, yw2;
if (rect_off_screen(x1,y1,x2,y2))
return;
if (gl_state.disp_type == SCREEN) {
/* translate to X Windows calling convention. */
xw1 = xworld_to_scrn(x1);
xw2 = xworld_to_scrn(x2);
yw1 = yworld_to_scrn(y1);
yw2 = yworld_to_scrn(y2);
#ifdef X11
unsigned int width, height;
int xl, yt;
xl = min(xw1,xw2);
yt = min(yw1,yw2);
width = abs (xw1-xw2);
height = abs (yw1-yw2);
XDrawRectangle(x11_state.display, x11_state.toplevel,
x11_state.current_gc, xl, yt, width, height);
#else /* Win32 */
if(xw1 > xw2) {
int temp = xw1;
xw1 = xw2;
xw2 = temp;
}
if(yw1 > yw2) {
int temp = yw1;
yw1 = yw2;
yw2 = temp;
}
HBRUSH hOldBrush;
/* NULL_BRUSH is a Windows stock object which does not fill any space. Thus, a *
* rectangle can be drawn by outlining it with the current pen and not filling *
* it since NULL_BRUSH is used. Another alternative to drawing a rectangle is *
* to draw four lines individually. */
hOldBrush = (HBRUSH)SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_BRUSH));
if(!(hOldBrush))
WIN32_SELECT_ERROR();
if(!Rectangle(win32_state.hGraphicsDC, xw1, yw1, xw2, yw2))
WIN32_DRAW_ERROR();
/* Need to restore the previous brush into the specified device context after *
* the rectangle is drawn. */
if(!SelectObject(win32_state.hGraphicsDC, hOldBrush))
WIN32_SELECT_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"%.2f %.2f %.2f %.2f drawrect\n",xworld_to_post(x1),yworld_to_post(y1),
xworld_to_post(x2),yworld_to_post(y2));
}
}
/* (x1,y1) and (x2,y2) are diagonally opposed corners in world coords. */
void
fillrect (float x1, float y1, float x2, float y2)
{
int xw1, yw1, xw2, yw2;
if (rect_off_screen(x1,y1,x2,y2))
return;
if (gl_state.disp_type == SCREEN) {
/* translate to X Windows calling convention. */
xw1 = xworld_to_scrn(x1);
xw2 = xworld_to_scrn(x2);
yw1 = yworld_to_scrn(y1);
yw2 = yworld_to_scrn(y2);
#ifdef X11
unsigned int width, height;
int xl, yt;
xl = min(xw1,xw2);
yt = min(yw1,yw2);
width = abs (xw1-xw2);
height = abs (yw1-yw2);
XFillRectangle(x11_state.display, x11_state.toplevel,
x11_state.current_gc, xl, yt, width, height);
#else /* Win32 */
if(xw1 > xw2) {
int temp = xw1;
xw1 = xw2;
xw2 = temp;
}
if(yw1 > yw2) {
int temp = yw1;
yw1 = yw2;
yw2 = temp;
}
if(!Rectangle(win32_state.hGraphicsDC, xw1, yw1, xw2, yw2))
WIN32_DRAW_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"%.2f %.2f %.2f %.2f fillrect\n",xworld_to_post(x1),
yworld_to_post(y1),xworld_to_post(x2),yworld_to_post(y2));
}
}
/* Normalizes an angle to be between 0 and 360 degrees. */
static float
angnorm (float ang)
{
int scale;
if (ang < 0) {
scale = (int) (ang / 360. - 1);
}
else {
scale = (int) (ang / 360.);
}
ang = ang - scale * 360;
return (ang);
}
void
drawellipticarc (float xc, float yc, float radx, float rady, float startang, float angextent)
{
int xl, yt;
unsigned int width, height;
/* Conservative (but fast) clip test -- check containing rectangle of *
* an ellipse. */
if (rect_off_screen (xc-radx,yc-rady,xc+radx,yc+rady))
return;
/* X Windows has trouble with very large angles. (Over 360). *
* Do following to prevent its inaccurate (overflow?) problems. */
if (fabs(angextent) > 360.)
angextent = 360.;
startang = angnorm (startang);
if (gl_state.disp_type == SCREEN) {
xl = (int) (xworld_to_scrn(xc) - fabs(trans_coord.wtos_xmult*radx));
yt = (int) (yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady));
width = (unsigned int) (2*fabs(trans_coord.wtos_xmult*radx));
height = (unsigned int) (2*fabs(trans_coord.wtos_ymult*rady));
#ifdef X11
XDrawArc (x11_state.display, x11_state.toplevel, x11_state.current_gc, xl, yt, width, height,
(int) (startang*64), (int) (angextent*64));
#else // Win32
int p1, p2, p3, p4;
/* set arc direction */
float startang_rad, endang_rad;
if (angextent > 0) {
startang_rad = (float) DEGTORAD(startang);
endang_rad = (float) DEGTORAD(startang+angextent-.001);
}
else {
startang_rad = (float) DEGTORAD(startang+angextent+.001);
endang_rad = (float) DEGTORAD(startang);
}
p1 = (int)(xworld_to_scrn(xc) + fabs(trans_coord.wtos_xmult*radx)*cos(startang_rad));
p2 = (int)(yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady)*sin(startang_rad));
p3 = (int)(xworld_to_scrn(xc) + fabs(trans_coord.wtos_xmult*radx)*cos(endang_rad));
p4 = (int)(yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady)*sin(endang_rad));
if(!Arc(win32_state.hGraphicsDC, xl, yt, xl+width, yt+height, p1, p2, p3, p4))
WIN32_DRAW_ERROR();
#endif
}
else {
fprintf(gl_state.ps, "gsave\n");
fprintf(gl_state.ps, "%.2f %.2f translate\n", xworld_to_post(xc), yworld_to_post(yc));
fprintf(gl_state.ps, "%.2f 1 scale\n",
fabs(radx*trans_coord.ps_xmult)/fabs(rady*trans_coord.ps_ymult));
fprintf(gl_state.ps, "0 0 %.2f %.2f %.2f %s\n", /*xworld_to_post(xc)*/
/*yworld_to_post(yc)*/ fabs(rady*trans_coord.ps_xmult), startang,
startang+angextent, (angextent < 0) ? "drawarcn" : "drawarc") ;
fprintf(gl_state.ps, "grestore\n");
}
}
/* Startang is relative to the Window's positive x direction. Angles in degrees.
*/
void
drawarc (float xc, float yc, float rad, float startang,
float angextent)
{
drawellipticarc(xc, yc, rad, rad, startang, angextent);
}
/* Fills a elliptic arc. Startang is relative to the Window's positive x
* direction. Angles in degrees.
*/
void
fillellipticarc (float xc, float yc, float radx, float rady, float startang,
float angextent)
{
int xl, yt;
unsigned int width, height;
/* Conservative (but fast) clip test -- check containing rectangle of *
* a circle. */
if (rect_off_screen (xc-radx,yc-rady,xc+radx,yc+rady))
return;
/* X Windows has trouble with very large angles. (Over 360). *
* Do following to prevent its inaccurate (overflow?) problems. */
if (fabs(angextent) > 360.)
angextent = 360.;
startang = angnorm (startang);
if (gl_state.disp_type == SCREEN) {
xl = (int) (xworld_to_scrn(xc) - fabs(trans_coord.wtos_xmult*radx));
yt = (int) (yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady));
width = (unsigned int) (2*fabs(trans_coord.wtos_xmult*radx));
height = (unsigned int) (2*fabs(trans_coord.wtos_ymult*rady));
#ifdef X11
XFillArc (x11_state.display, x11_state.toplevel, x11_state.current_gc, xl, yt, width, height,
(int) (startang*64), (int) (angextent*64));
#else // Win32
HPEN hOldPen;
int p1, p2, p3, p4;
/* set pie direction */
float startang_rad, endang_rad;
if (angextent > 0) {
startang_rad = (float) DEGTORAD(startang);
endang_rad = (float) DEGTORAD(startang+angextent-.001);
}
else {
startang_rad = (float) DEGTORAD(startang+angextent+.001);
endang_rad = (float) DEGTORAD(startang);
}
p1 = (int)(xworld_to_scrn(xc) + fabs(trans_coord.wtos_xmult*radx)*cos(startang_rad));
p2 = (int)(yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady)*sin(startang_rad));
p3 = (int)(xworld_to_scrn(xc) + fabs(trans_coord.wtos_xmult*radx)*cos(endang_rad));
p4 = (int)(yworld_to_scrn(yc) - fabs(trans_coord.wtos_ymult*rady)*sin(endang_rad));
/* NULL_PEN is a Windows stock object which does not draw anything. Set current *
* pen to NULL_PEN in order to fill the arc without drawing the outline. */
hOldPen = (HPEN)SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_PEN));
if(!(hOldPen))
WIN32_SELECT_ERROR();
// Win32 API says a zero return value indicates an error, but it seems to always
// return zero. Don't check for an error on Pie.
Pie(win32_state.hGraphicsDC, xl, yt, xl+width, yt+height, p1, p2, p3, p4);
//if(!Pie(win32_state.hGraphicsDC, xl, yt, xl+width, yt+height, p1, p2, p3, p4));
//WIN32_DRAW_ERROR();
/* Need to restore the original pen into the device context after filling. */
if(!SelectObject(win32_state.hGraphicsDC, hOldPen))
WIN32_SELECT_ERROR();
#endif
}
else {
fprintf(gl_state.ps, "gsave\n");
fprintf(gl_state.ps, "%.2f %.2f translate\n", xworld_to_post(xc), yworld_to_post(yc));
fprintf(gl_state.ps, "%.2f 1 scale\n",
fabs(radx*trans_coord.ps_xmult)/fabs(rady*trans_coord.ps_ymult));
fprintf(gl_state.ps, "%.2f %.2f %.2f 0 0 %s\n", /*xworld_to_post(xc)*/
/*yworld_to_post(yc)*/ fabs(rady*trans_coord.ps_xmult), startang,
startang+angextent, (angextent < 0) ? "fillarcn" : "fillarc") ;
fprintf(gl_state.ps, "grestore\n");
}
}
void
fillarc (float xc, float yc, float rad, float startang, float angextent) {
fillellipticarc(xc, yc, rad, rad, startang, angextent);
}
void
fillpoly (t_point *points, int npoints)
{
#ifdef X11
XPoint transpoints[MAXPTS];
#else
POINT transpoints[MAXPTS];
#endif
int i;
float xmin, ymin, xmax, ymax;
if (npoints > MAXPTS) {
printf("Error in fillpoly: Only %d points allowed per polygon.\n",
MAXPTS);
printf("%d points were requested. Polygon is not drawn.\n",npoints);
return;
}
/* Conservative (but fast) clip test -- check containing rectangle of *
* polygon. */
xmin = xmax = points[0].x;
ymin = ymax = points[0].y;
for (i=1;i<npoints;i++) {
xmin = min (xmin,points[i].x);
xmax = max (xmax,points[i].x);
ymin = min (ymin,points[i].y);
ymax = max (ymax,points[i].y);
}
if (rect_off_screen(xmin,ymin,xmax,ymax))
return;
if (gl_state.disp_type == SCREEN) {
for (i=0;i<npoints;i++) {
transpoints[i].x = (long) xworld_to_scrn (points[i].x);
transpoints[i].y = (long) yworld_to_scrn (points[i].y);
}
#ifdef X11
XFillPolygon(x11_state.display, x11_state.toplevel, x11_state.current_gc,
transpoints, npoints, Complex, CoordModeOrigin);
#else
HPEN hOldPen;
/* NULL_PEN is a Windows stock object which does not draw anything. Set current *
* pen to NULL_PEN in order to fill polygon without drawing its outline. */
hOldPen = (HPEN)SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_PEN));
if(!(hOldPen))
WIN32_SELECT_ERROR();
if(!Polygon (win32_state.hGraphicsDC, transpoints, npoints))
WIN32_DRAW_ERROR();
/* Need to restore the original pen into the device context after drawing. */
if(!SelectObject(win32_state.hGraphicsDC, hOldPen))
WIN32_SELECT_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"\n");
for (i=npoints-1;i>=0;i--)
fprintf (gl_state.ps, "%.2f %.2f\n", xworld_to_post(points[i].x),
yworld_to_post(points[i].y));
fprintf (gl_state.ps, "%d fillpoly\n", npoints);
}
}
/* Draws text centered on xc,yc if it fits in boundx */
void
drawtext (float xc, float yc, const char *text, float boundx)
{
int len, width, xw_off, yw_off, font_ascent, font_descent;
#ifdef X11
len = strlen(text);
width = XTextWidth(x11_state.font_info[gl_state.currentfontsize], text, len);
font_ascent = x11_state.font_info[gl_state.currentfontsize]->ascent;
font_descent = x11_state.font_info[gl_state.currentfontsize]->descent;
#else /* WC : WIN32 */
SIZE textsize;
TEXTMETRIC textmetric;
if(SetTextColor(win32_state.hGraphicsDC, win32_colors[gl_state.currentcolor]) == CLR_INVALID)
WIN32_DRAW_ERROR();
len = strlen(text);
if (!GetTextExtentPoint32(win32_state.hGraphicsDC, text, len, &textsize))
WIN32_DRAW_ERROR();
width = textsize.cx;
if (!GetTextMetrics(win32_state.hGraphicsDC, &textmetric))
WIN32_DRAW_ERROR();
font_ascent = textmetric.tmAscent;
font_descent = textmetric.tmDescent;
#endif
if (width > fabs(boundx*trans_coord.wtos_xmult))
return; /* Don't draw if it won't fit */
xw_off = (int)(width/(2.*trans_coord.wtos_xmult)); /* NB: sign doesn't matter. */
/* NB: 2 * descent makes this slightly conservative but simplifies code. */
yw_off = (int)((font_ascent + 2 * font_descent)/(2.*trans_coord.wtos_ymult));
/* Note: text can be clipped when a little bit of it would be visible *
* right now. Perhaps X doesn't return extremely accurate width and *
* ascent values, etc? Could remove this completely by multiplying *
* xw_off and yw_off by, 1.2 or 1.5. */
if (rect_off_screen(xc-xw_off, yc-yw_off, xc+xw_off, yc+yw_off))
return;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
XDrawString(x11_state.display, x11_state.toplevel, x11_state.current_gc,
xworld_to_scrn(xc)-width/2,
yworld_to_scrn(yc)+(x11_state.font_info[gl_state.currentfontsize]->ascent
-x11_state.font_info[gl_state.currentfontsize]->descent)/2,
text, len);
#else /* Win32 */
SetBkMode(win32_state.hGraphicsDC, TRANSPARENT);
if(!TextOut (win32_state.hGraphicsDC, xworld_to_scrn(xc)-width/2,
yworld_to_scrn(yc) - (font_ascent + font_descent)/2, text, len))
WIN32_DRAW_ERROR();
#endif
}
else {
fprintf(gl_state.ps,"(%s) %.2f %.2f censhow\n",text,xworld_to_post(xc),yworld_to_post(yc));
}
}
void
flushinput (void)
{
if (gl_state.disp_type != SCREEN)
return;
#ifdef X11
XFlush(x11_state.display);
#endif
}
void
init_world (float x1, float y1, float x2, float y2)
{
/* Sets the coordinate system the user wants to draw into. */
trans_coord.xleft = x1;
trans_coord.xright = x2;
trans_coord.ytop = y1;
trans_coord.ybot = y2;
/* Save initial world coordinates to allow full view button
* to zoom all the way out.
*/
trans_coord.init_xleft = trans_coord.xleft;
trans_coord.init_xright = trans_coord.xright;
trans_coord.init_ytop = trans_coord.ytop;
trans_coord.init_ybot = trans_coord.ybot;
if (gl_state.disp_type == SCREEN) {
update_transform();
}
else {
update_ps_transform();
}
}
/* Draw the current message in the text area at the screen bottom. */
void
draw_message (void)
{
int savefontsize, savecolor;
float ylow;
#ifdef X11
int len, width;
#endif
if (gl_state.disp_type == SCREEN) {
#ifdef X11
XClearWindow (x11_state.display, x11_state.textarea);
len = strlen (gl_state.statusMessage);
width = XTextWidth(x11_state.font_info[MENU_FONT_SIZE], gl_state.statusMessage, len);
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[WHITE]);
XDrawRectangle(x11_state.display, x11_state.textarea, x11_state.gc_menus, 0, 0,
trans_coord.top_width - MWIDTH, T_AREA_HEIGHT);
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
XDrawLine(x11_state.display, x11_state.textarea, x11_state.gc_menus, 0, T_AREA_HEIGHT-1,
trans_coord.top_width-MWIDTH, T_AREA_HEIGHT-1);
XDrawLine(x11_state.display, x11_state.textarea, x11_state.gc_menus,
trans_coord.top_width-MWIDTH-1, 0, trans_coord.top_width-MWIDTH-1,
T_AREA_HEIGHT-1);
XDrawString(x11_state.display, x11_state.textarea, x11_state.gc_menus,
(trans_coord.top_width - MWIDTH - width)/2,
T_AREA_HEIGHT/2 + (x11_state.font_info[MENU_FONT_SIZE]->ascent -
x11_state.font_info[MENU_FONT_SIZE]->descent)/2, gl_state.statusMessage, len);
#else
if(!InvalidateRect(win32_state.hStatusWnd, NULL, TRUE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hStatusWnd))
WIN32_DRAW_ERROR();
#endif
}
else {
/* Draw the message in the bottom margin. Printer's generally can't *
* print on the bottom 1/4" (area with y < 18 in PostScript coords.) */
savecolor = gl_state.currentcolor;
setcolor (BLACK);
savefontsize = gl_state.currentfontsize;
setfontsize (MENU_FONT_SIZE - 2); /* Smaller OK on paper */
ylow = trans_coord.ps_bot - 8;
fprintf(gl_state.ps,"(%s) %.2f %.2f censhow\n",gl_state.statusMessage,
(trans_coord.ps_left+trans_coord.ps_right)/2., ylow);
setcolor (savecolor);
setfontsize (savefontsize);
}
}
/* Changes the message to be displayed on screen. */
void
update_message (const char *msg)
{
strncpy (gl_state.statusMessage, msg, BUFSIZE);
draw_message ();
#ifdef X11
// Make this appear immediately. Win32 does that automaticaly.
XFlush (x11_state.display);
#endif // X11
}
/* Zooms in menu button pressed. Zoom in at center
* of graphics area.
*/
static void
zoom_in (void (*drawscreen) (void))
{
float xcen, ycen;
xcen = (trans_coord.xright + trans_coord.xleft)/2;
ycen = (trans_coord.ybot + trans_coord.ytop)/2;
handle_zoom_in(xcen, ycen, drawscreen);
}
/* Zoom out menu button pressed. Zoom out from center
* of graphics area.
*/
static void
zoom_out (void (*drawscreen) (void))
{
float xcen, ycen;
xcen = (trans_coord.xright + trans_coord.xleft)/2;
ycen = (trans_coord.ybot + trans_coord.ytop)/2;
handle_zoom_out(xcen, ycen, drawscreen);
}
/* Zooms in by a factor of ZOOM_FACTOR */
static void
handle_zoom_in (float x, float y, void (*drawscreen) (void))
{
//make xright - xleft = 0.6 of the original distance
trans_coord.xleft = x - (x-trans_coord.xleft)/(float) ZOOM_FACTOR;
trans_coord.xright = x + (trans_coord.xright-x)/(float) ZOOM_FACTOR;
//make ybot - ytop = 0.6 of the original distance
trans_coord.ytop = y - (y-trans_coord.ytop)/(float) ZOOM_FACTOR;
trans_coord.ybot = y + (trans_coord.ybot -y)/(float) ZOOM_FACTOR;
update_transform ();
drawscreen();
}
/* Zooms out by a factor of ZOOM_FACTOR */
static void
handle_zoom_out (float x, float y, void (*drawscreen) (void))
{
//restore the original distances before previous zoom in
trans_coord.xleft = x - (x-trans_coord.xleft)*((float) ZOOM_FACTOR);
trans_coord.xright = x + (trans_coord.xright-x)*((float) ZOOM_FACTOR);
trans_coord.ytop = y - (y-trans_coord.ytop)*((float) ZOOM_FACTOR);
trans_coord.ybot = y + (trans_coord.ybot -y)*((float) ZOOM_FACTOR);
update_transform ();
drawscreen();
}
/* Sets the view back to the initial view set by init_world (i.e. a full *
* view) of all the graphics. */
static void
zoom_fit (void (*drawscreen) (void))
{
trans_coord.xleft = trans_coord.init_xleft;
trans_coord.xright = trans_coord.init_xright;
trans_coord.ytop = trans_coord.init_ytop;
trans_coord.ybot = trans_coord.init_ybot;
update_transform ();
drawscreen();
}
/* Moves view 1/2 screen up. */
static void
translate_up (void (*drawscreen) (void))
{
float ystep;
ystep = (trans_coord.ybot - trans_coord.ytop)/2;
trans_coord.ytop -= ystep;
trans_coord.ybot -= ystep;
update_transform();
drawscreen();
}
/* Moves view 1/2 screen down. */
static void
translate_down (void (*drawscreen) (void))
{
float ystep;
ystep = (trans_coord.ybot - trans_coord.ytop)/2;
trans_coord.ytop += ystep;
trans_coord.ybot += ystep;
update_transform();
drawscreen();
}
/* Moves view 1/2 screen left. */
static void
translate_left (void (*drawscreen) (void))
{
float xstep;
xstep = (trans_coord.xright - trans_coord.xleft)/2;
trans_coord.xleft -= xstep;
trans_coord.xright -= xstep;
update_transform();
drawscreen();
}
/* Moves view 1/2 screen right. */
static void
translate_right (void (*drawscreen) (void))
{
float xstep;
xstep = (trans_coord.xright - trans_coord.xleft)/2;
trans_coord.xleft += xstep;
trans_coord.xright += xstep;
update_transform();
drawscreen();
}
/* Panning is enabled by pressing and holding down mouse wheel
* (or middle mouse button)
*/
static void
panning_execute (int x, int y, void (*drawscreen) (void))
{
float x_change_world, y_change_world;
x_change_world = xscrn_to_world (x) - xscrn_to_world (pan_state.previous_x);
y_change_world = yscrn_to_world (y) - yscrn_to_world (pan_state.previous_y);
trans_coord.xleft -= x_change_world;
trans_coord.xright -= x_change_world;
trans_coord.ybot -= y_change_world;
trans_coord.ytop -= y_change_world;
update_transform();
drawscreen();
pan_state.previous_x = x;
pan_state.previous_y = y;
}
/* Turn panning_enabled on */
static void
panning_on (int start_x, int start_y)
{
pan_state.previous_x = start_x;
pan_state.previous_y = start_y;
pan_state.panning_enabled = true;
}
/* Turn panning_enabled off */
static void
panning_off (void)
{
pan_state.panning_enabled = false;
}
// Updates the graphics transformation so that the graphics drawn within the
// box (in pixels) defined by x[0],y[0] to x[1],y[1] will be scaled to fill
// the whole window area.
static void
update_win (int x[2], int y[2], void (*drawscreen)(void))
{
float x1, x2, y1, y2;
x[0] = min(x[0],trans_coord.top_width-MWIDTH); /* Can't go under menu */
x[1] = min(x[1],trans_coord.top_width-MWIDTH);
y[0] = min(y[0],trans_coord.top_height-T_AREA_HEIGHT); /* Can't go under text area */
y[1] = min(y[1],trans_coord.top_height-T_AREA_HEIGHT);
if ((x[0] == x[1]) || (y[0] == y[1])) {
printf("Illegal (zero area) window. Window unchanged.\n");
return;
}
x1 = xscrn_to_world(min(x[0],x[1]));
x2 = xscrn_to_world(max(x[0],x[1]));
y1 = yscrn_to_world(min(y[0],y[1]));
y2 = yscrn_to_world(max(y[0],y[1]));
trans_coord.xleft = x1;
trans_coord.xright = x2;
trans_coord.ytop = y1;
trans_coord.ybot = y2;
update_transform();
drawscreen();
}
/* The window button was pressed. Let the user click on the two *
* diagonally opposed corners, and zoom in on this area. */
static void
adjustwin (void (*drawscreen) (void))
{
#ifdef X11
XEvent report;
int corner, xold, yold, x[2], y[2];
corner = 0;
xold = -1;
yold = -1; /* Don't need to init yold, but stops compiler warning. */
while (corner<2) {
XNextEvent (x11_state.display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0)
break;
x11_handle_expose(report, drawscreen);
if (report.xexpose.window == x11_state.toplevel) {
xold = -1; /* No rubber band on screen */
}
break;
case ConfigureNotify:
x11_handle_configure_notify(report);
break;
case ButtonPress:
#ifdef VERBOSE
printf("Got a buttonpress.\n");
printf("Window ID is: %ld.\n",report.xbutton.window);
printf("Location (%d, %d).\n", report.xbutton.x,
report.xbutton.y);
#endif
if (report.xbutton.window != x11_state.toplevel) break;
x[corner] = report.xbutton.x;
y[corner] = report.xbutton.y;
if (corner == 0) {
/* XSelectInput (x11_state.display, x11_state.toplevel, ExposureMask |
StructureNotifyMask | ButtonPressMask | PointerMotionMask); */
}
else {
update_win(x,y,drawscreen);
}
corner++;
break;
case MotionNotify:
if (corner) {
#ifdef VERBOSE
printf("Got a MotionNotify Event.\n");
printf("x: %d y: %d\n",report.xmotion.x,report.xmotion.y);
#endif
if (xold >= 0) { /* xold set -ve before we draw first box */
// Erase prior box.
set_draw_mode(DRAW_XOR);
XDrawRectangle(x11_state.display,x11_state.toplevel, x11_state.gc_xor,
min(x[0],xold), min(y[0],yold), abs(x[0]-xold), abs(y[0]-yold));
set_draw_mode(DRAW_NORMAL);
}
/* Don't allow user to window under menu region */
xold = min(report.xmotion.x,trans_coord.top_width-1-MWIDTH);
yold = report.xmotion.y;
set_draw_mode(DRAW_XOR);
// Use forcing versions, as we want these modes to apply
// to the xor drawing context, and the regular versions
// won't update the drawing context if there is no change in line
// width etc. (but they might only be on the normal context)
force_setlinewidth(1);
force_setlinestyle(DASHED);
force_setcolor(gl_state.background_cindex);
// Draw rubber band box.
XDrawRectangle(x11_state.display,x11_state.toplevel,x11_state.gc_xor,min(x[0],xold),
min(y[0],yold),abs(x[0]-xold),abs(y[0]-yold));
set_draw_mode(DRAW_NORMAL);
}
break;
}
}
/* XSelectInput (x11_state.display, x11_state.toplevel, ExposureMask | StructureNotifyMask
| ButtonPressMask); */
#else /* Win32 */
/* Implemented as WM_LB... events */
/* Begin window adjust */
if (win32_state.windowAdjustFlag == WINDOW_DEACTIVATED) {
win32_state.windowAdjustFlag = WAITING_FOR_FIRST_CORNER_POINT;
}
#endif
}
static void
postscript (void (*drawscreen) (void))
{
/* Takes a snapshot of the screen and stores it in pic?.ps. The *
* first picture goes in pic1.ps, the second in pic2.ps, etc. */
static int piccount = 1;
int success;
char fname[BUFSIZE];
sprintf(fname,"pic%d.ps",piccount);
printf("Writing postscript output to file %s\n", fname);
success = init_postscript (fname);
if (success) {
drawscreen();
close_postscript ();
piccount++;
}
else {
printf ("Error initializing for postscript output.\n");
#ifdef WIN32
MessageBox(win32_state.hMainWnd, "Error initializing postscript output.", NULL, MB_OK);
#endif
}
}
static void
proceed (void (*drawscreen) (void))
{
gl_state.ProceedPressed = true;
}
static void
quit (void (*drawscreen) (void))
{
close_graphics();
exit(0);
}
/* Release all my drawing structures (through the X server) and *
* close down the connection. */
void
close_graphics (void)
{
if (!gl_state.initialized)
return;
#ifdef X11
int i;
for (i=0;i<=MAX_FONT_SIZE;i++) {
if (gl_state.font_is_loaded[i])
XFreeFont(x11_state.display,x11_state.font_info[i]);
}
XFreeGC(x11_state.display,x11_state.gc_normal);
XFreeGC(x11_state.display,x11_state.gc_xor);
XFreeGC(x11_state.display,x11_state.gc_menus);
if (x11_state.private_cmap != None)
XFreeColormap (x11_state.display, x11_state.private_cmap);
XCloseDisplay(x11_state.display);
#else /* Win32 */
int i;
// Free the font data structure for each loaded font.
for (i = 0; i <= MAX_FONT_SIZE; i++) {
if (gl_state.font_is_loaded[i]) {
free (win32_state.font_info[i]);
}
}
// Destroy the window
if(!DestroyWindow(win32_state.hMainWnd))
WIN32_DRAW_ERROR();
// free the window class (type information held by MS Windows)
// for each of the four window types we created. Otherwise a
// later call to init_graphics to open the graphics window up again
// will fail.
if (!UnregisterClass (szAppName, GetModuleHandle(NULL)) )
WIN32_DRAW_ERROR();
if (!UnregisterClass (szGraphicsName, GetModuleHandle(NULL)) )
WIN32_DRAW_ERROR();
if (!UnregisterClass (szStatusName, GetModuleHandle(NULL)) )
WIN32_DRAW_ERROR();
if (!UnregisterClass (szButtonsName, GetModuleHandle(NULL)) )
WIN32_DRAW_ERROR();
#endif
free(button_state.button);
button_state.button = NULL;
for (i = 0; i <= MAX_FONT_SIZE; i++) {
gl_state.font_is_loaded[i] = false;
#ifdef X11
x11_state.font_info[i] = NULL;
#else /* Win32 */
win32_state.font_info[i] = NULL;
#endif
}
gl_state.initialized = false;
}
/* Opens a file for PostScript output. The header information, *
* clipping path, etc. are all dumped out. If the file could *
* not be opened, the routine returns 0; otherwise it returns 1. */
int init_postscript (const char *fname)
{
gl_state.ps = fopen (fname,"w");
if (gl_state.ps == NULL) {
printf("Error: could not open %s for PostScript output.\n",fname);
printf("Drawing to screen instead.\n");
return (0);
}
gl_state.disp_type = POSTSCRIPT; /* Graphics go to postscript file now. */
/* Header for minimal conformance with the Adobe structuring convention */
fprintf(gl_state.ps,"%%!PS-Adobe-1.0\n");
fprintf(gl_state.ps,"%%%%DocumentFonts: Helvetica\n");
fprintf(gl_state.ps,"%%%%Pages: 1\n");
/* Set up postscript transformation macros and page boundaries */
update_ps_transform();
/* Bottom margin is at ps_bot - 15. to leave room for the on-screen message. */
fprintf(gl_state.ps,"%%%%HiResBoundingBox: %.2f %.2f %.2f %.2f\n",
trans_coord.ps_left, trans_coord.ps_bot - 15.,
trans_coord.ps_right, trans_coord.ps_top);
fprintf(gl_state.ps,"%%%%EndComments\n");
fprintf(gl_state.ps,"/censhow %%draw a centered string\n");
fprintf(gl_state.ps," { moveto %% move to proper spot\n");
fprintf(gl_state.ps," dup stringwidth pop %% get x length of string\n");
fprintf(gl_state.ps," -2 div %% Proper left start\n");
fprintf(gl_state.ps," yoff rmoveto %% Move left that much and down half font height\n");
fprintf(gl_state.ps," show newpath } def %% show the string\n\n");
fprintf(gl_state.ps,"/setfontsize %% set font to desired size and compute "
"centering yoff\n");
fprintf(gl_state.ps," { /Helvetica findfont\n");
fprintf(gl_state.ps," exch scalefont\n");
fprintf(gl_state.ps," setfont %% Font size set ...\n\n");
fprintf(gl_state.ps," 0 0 moveto %% Get vertical centering offset\n");
fprintf(gl_state.ps," (Xg) true charpath\n");
fprintf(gl_state.ps," flattenpath pathbbox\n");
fprintf(gl_state.ps," /ascent exch def pop -1 mul /descent exch def pop\n");
fprintf(gl_state.ps," newpath\n");
fprintf(gl_state.ps," descent ascent sub 2 div /yoff exch def } def\n\n");
fprintf(gl_state.ps,"%% Next two lines for debugging only.\n");
fprintf(gl_state.ps,"/str 20 string def\n");
fprintf(gl_state.ps,"/pnum {str cvs print ( ) print} def\n");
fprintf(gl_state.ps,"/drawline %% draw a line from (x2,y2) to (x1,y1)\n");
fprintf(gl_state.ps," { moveto lineto stroke } def\n\n");
fprintf(gl_state.ps,"/rect %% outline a rectangle \n");
fprintf(gl_state.ps," { /y2 exch def /x2 exch def /y1 exch def /x1 exch def\n");
fprintf(gl_state.ps," x1 y1 moveto\n");
fprintf(gl_state.ps," x2 y1 lineto\n");
fprintf(gl_state.ps," x2 y2 lineto\n");
fprintf(gl_state.ps," x1 y2 lineto\n");
fprintf(gl_state.ps," closepath } def\n\n");
fprintf(gl_state.ps,"/drawrect %% draw outline of a rectanagle\n");
fprintf(gl_state.ps," { rect stroke } def\n\n");
fprintf(gl_state.ps,"/fillrect %% fill in a rectanagle\n");
fprintf(gl_state.ps," { rect fill } def\n\n");
fprintf (gl_state.ps,"/drawarc { arc stroke } def %% draw an arc\n");
fprintf (gl_state.ps,"/drawarcn { arcn stroke } def "
" %% draw an arc in the opposite direction\n\n");
fprintf (gl_state.ps,"%%Fill a counterclockwise or clockwise arc sector, "
"respectively.\n");
fprintf (gl_state.ps,"/fillarc { moveto currentpoint 5 2 roll arc closepath fill } "
"def\n");
fprintf (gl_state.ps,"/fillarcn { moveto currentpoint 5 2 roll arcn closepath fill } "
"def\n\n");
fprintf (gl_state.ps,"/fillpoly { 3 1 roll moveto %% move to first point\n"
" 2 exch 1 exch {pop lineto} for %% line to all other points\n"
" closepath fill } def\n\n");
fprintf(gl_state.ps,"%%Color Definitions:\n");
fprintf(gl_state.ps,"/white { 1 setgray } def\n");
fprintf(gl_state.ps,"/black { 0 setgray } def\n");
fprintf(gl_state.ps,"/grey55 { .55 setgray } def\n");
fprintf(gl_state.ps,"/grey75 { .75 setgray } def\n");
fprintf(gl_state.ps,"/blue { 0 0 1 setrgbcolor } def\n");
fprintf(gl_state.ps,"/green { 0 1 0 setrgbcolor } def\n");
fprintf(gl_state.ps,"/yellow { 1 1 0 setrgbcolor } def\n");
fprintf(gl_state.ps,"/cyan { 0 1 1 setrgbcolor } def\n");
fprintf(gl_state.ps,"/red { 1 0 0 setrgbcolor } def\n");
fprintf(gl_state.ps,"/pink { 1 0.75 0.8 setrgbcolor } def\n");
fprintf(gl_state.ps,"/lightpink { 1 0.71 0.76 setrgbcolor } def\n");
fprintf(gl_state.ps,"/darkgreen { 0 0.5 0 setrgbcolor } def\n");
fprintf(gl_state.ps,"/magenta { 1 0 1 setrgbcolor } def\n");
fprintf(gl_state.ps,"/bisque { 1 0.89 0.77 setrgbcolor } def\n");
fprintf(gl_state.ps,"/lightskyblue { 0.53 0.81 0.98 setrgbcolor } def\n");
fprintf(gl_state.ps,"/thistle { 0.85 0.75 0.85 setrgbcolor } def\n");
fprintf(gl_state.ps,"/plum { 0.87 0.63 0.87 setrgbcolor } def\n");
fprintf(gl_state.ps,"/khaki { 0.94 0.9 0.55 setrgbcolor } def\n");
fprintf(gl_state.ps,"/coral { 1 0.5 0.31 setrgbcolor } def\n");
fprintf(gl_state.ps,"/turquoise { 0.25 0.88 0.82 setrgbcolor } def\n");
fprintf(gl_state.ps,"/mediumpurple { 0.58 0.44 0.86 setrgbcolor } def\n");
fprintf(gl_state.ps,"/darkslateblue { 0.28 0.24 0.55 setrgbcolor } def\n");
fprintf(gl_state.ps,"/darkkhaki { 0.74 0.72 0.42 setrgbcolor } def\n");
fprintf(gl_state.ps,"\n%%Solid and dashed line definitions:\n");
fprintf(gl_state.ps,"/linesolid {[] 0 setdash} def\n");
fprintf(gl_state.ps,"/linedashed {[3 3] 0 setdash} def\n");
fprintf(gl_state.ps,"\n%%%%EndProlog\n");
fprintf(gl_state.ps,"%%%%Page: 1 1\n\n");
/* Set up PostScript graphics state to match current one. */
force_setcolor (gl_state.currentcolor);
force_setlinestyle (gl_state.currentlinestyle);
force_setlinewidth (gl_state.currentlinewidth);
force_setfontsize (gl_state.currentfontsize);
/* Draw this in the bottom margin -- must do before the clippath is set */
draw_message ();
/* Set clipping on page. */
fprintf(gl_state.ps,"%.2f %.2f %.2f %.2f rect ",trans_coord.ps_left, trans_coord.ps_bot,
trans_coord.ps_right, trans_coord.ps_top);
fprintf(gl_state.ps,"clip newpath\n\n");
return (1);
}
/* Properly ends postscript output and redirects output to screen. */
void close_postscript (void)
{
fprintf(gl_state.ps,"showpage\n");
fprintf(gl_state.ps,"\n%%%%Trailer\n");
fclose (gl_state.ps);
gl_state.disp_type = SCREEN;
update_transform(); /* Ensure screen world reflects any changes *
* made while printing. */
/* Need to make sure that we really set up the graphics context.
* The current font set indicates the last font used in a postscript call,
* etc., *NOT* the font set in the X11 or Win32 graphics context. Force the
* current font, colour etc. to be applied to the graphics context, so
* subsequent drawing commands work properly.
*/
force_setcolor (gl_state.currentcolor);
force_setlinestyle (gl_state.currentlinestyle);
force_setlinewidth (gl_state.currentlinewidth);
force_setfontsize (gl_state.currentfontsize);
}
/* Sets up the default menu buttons on the right hand side of the window. */
static void
build_default_menu (void)
{
int i, xcen, x1, y1, bwid, bheight, space;
const int NUM_ARROW_BUTTONS = 4, NUM_STANDARD_BUTTONS = 12, SEPARATOR_BUTTON_INDEX = 8;
#ifdef X11
unsigned long valuemask;
XSetWindowAttributes menu_attributes;
x11_state.menu = XCreateSimpleWindow(x11_state.display,x11_state.toplevel,
trans_coord.top_width-MWIDTH, 0, MWIDTH,
trans_coord.display_height, 0, x11_state.colors[BLACK],
x11_state.colors[LIGHTGREY]);
menu_attributes.event_mask = ExposureMask;
/* Ignore button presses on the menu background. */
menu_attributes.do_not_propagate_mask = ButtonPressMask;
/* Keep menu on top right */
menu_attributes.win_gravity = NorthEastGravity;
valuemask = CWWinGravity | CWEventMask | CWDontPropagate;
XChangeWindowAttributes(x11_state.display, x11_state.menu, valuemask, &menu_attributes);
XMapWindow (x11_state.display, x11_state.menu);
#endif
button_state.button = (t_button *) my_malloc (NUM_STANDARD_BUTTONS * sizeof (t_button));
/* Now do the arrow buttons */
bwid = 28;
space = 3;
y1 = 10;
xcen = 51;
x1 = xcen - bwid/2;
button_state.button[0].xleft = x1;
button_state.button[0].ytop = y1;
#ifdef X11
setpoly (0, bwid/2, bwid/2, bwid/3, -PI/2.); /* Up */
#else
button_state.button[0].type = BUTTON_TEXT;
strcpy(button_state.button[0].text, "U");
#endif
button_state.button[0].fcn = translate_up;
y1 += bwid + space;
x1 = xcen - 3*bwid/2 - space;
button_state.button[1].xleft = x1;
button_state.button[1].ytop = y1;
#ifdef X11
setpoly (1, bwid/2, bwid/2, bwid/3, PI); /* Left */
#else
button_state.button[1].type = BUTTON_TEXT;
strcpy(button_state.button[1].text, "L");
#endif
button_state.button[1].fcn = translate_left;
x1 = xcen + bwid/2 + space;
button_state.button[2].xleft = x1;
button_state.button[2].ytop = y1;
#ifdef X11
setpoly (2, bwid/2, bwid/2, bwid/3, 0); /* Right */
#else
button_state.button[2].type = BUTTON_TEXT;
strcpy(button_state.button[2].text, "R");
#endif
button_state.button[2].fcn = translate_right;
y1 += bwid + space;
x1 = xcen - bwid/2;
button_state.button[3].xleft = x1;
button_state.button[3].ytop = y1;
#ifdef X11
setpoly (3, bwid/2, bwid/2, bwid/3, +PI/2.); /* Down */
#else
button_state.button[3].type = BUTTON_TEXT;
strcpy(button_state.button[3].text, "D");
#endif
button_state.button[3].fcn = translate_down;
for (i = 0; i < NUM_ARROW_BUTTONS; i++) {
button_state.button[i].width = bwid;
button_state.button[i].height = bwid;
button_state.button[i].enabled = 1;
}
/* Rectangular buttons */
y1 += bwid + space + 6;
space = 8;
bwid = 90;
bheight = 26;
x1 = xcen - bwid/2;
for (i = NUM_ARROW_BUTTONS; i < NUM_STANDARD_BUTTONS; i++) {
button_state.button[i].xleft = x1;
button_state.button[i].ytop = y1;
button_state.button[i].type = BUTTON_TEXT;
button_state.button[i].width = bwid;
button_state.button[i].enabled = 1;
if (i != SEPARATOR_BUTTON_INDEX) {
button_state.button[i].height = bheight;
y1 += bheight + space;
}
else {
button_state.button[i].height = 2;
button_state.button[i].type = BUTTON_SEPARATOR;
y1 += 2 + space;
}
}
strcpy (button_state.button[4].text,"Zoom In");
strcpy (button_state.button[5].text,"Zoom Out");
strcpy (button_state.button[6].text,"Zoom Fit");
strcpy (button_state.button[7].text,"Window");
strcpy (button_state.button[8].text,"---1");
strcpy (button_state.button[9].text,"PostScript");
strcpy (button_state.button[10].text,"Proceed");
strcpy (button_state.button[11].text,"Exit");
button_state.button[4].fcn = zoom_in;
button_state.button[5].fcn = zoom_out;
button_state.button[6].fcn = zoom_fit;
button_state.button[7].fcn = adjustwin; // see 'adjustButton' below in WIN32 section
button_state.button[8].fcn = NULL;
button_state.button[9].fcn = postscript;
button_state.button[10].fcn = proceed;
button_state.button[11].fcn = quit;
for (i = 0; i < NUM_STANDARD_BUTTONS; i++)
map_button (i);
button_state.num_buttons = NUM_STANDARD_BUTTONS;
#ifdef WIN32
win32_state.adjustButton = 7;
if(!InvalidateRect(win32_state.hButtonsWnd, NULL, TRUE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hButtonsWnd))
WIN32_DRAW_ERROR();
#endif
}
/* Makes sure the font of the specified size is loaded. Point_size *
* MUST be between 1 and MAX_FONT_SIZE. */
static void
load_font(int pointsize)
{
if (pointsize > MAX_FONT_SIZE || pointsize < 1) {
printf ("Error: font size %d is out of valid range, 1 to %d.\n",
pointsize, MAX_FONT_SIZE);
return;
}
if (gl_state.font_is_loaded[pointsize]) // Nothing to do.
return;
#ifdef X11
#define NUM_FONT_TYPES 3
char fontname[NUM_FONT_TYPES][BUFSIZE];
int ifont;
bool success = false;
/* Use proper point-size medium-weight upright helvetica font */
// Exists on most X11 systems.
// Backup font: lucidasans, in the new naming style.
sprintf(fontname[0],"-*-helvetica-medium-r-*--*-%d0-*-*-*-*-*-*",
pointsize);
sprintf(fontname[1], "lucidasans-%d", pointsize);
sprintf(fontname[2],"-schumacher-clean-medium-r-*--*-%d0-*-*-*-*-*-*",
pointsize);
for (ifont = 0; ifont < NUM_FONT_TYPES; ifont++) {
#ifdef VERBOSE
printf ("Loading font: point size: %d, fontname: %s\n",pointsize,
fontname[ifont]);
#endif
/* Load font and get font information structure. */
x11_state.font_info[pointsize] = XLoadQueryFont(x11_state.display,fontname[ifont]);
if (x11_state.font_info[pointsize] == NULL) {
#ifdef VERBOSE
fprintf( stderr, "Cannot open font %s\n", fontname[ifont]);
#endif
}
else {
success = true;
break;
}
}
if (!success) {
printf ("Error in load_font: cannot load any font of pointsize %d.\n",
pointsize);
printf ("Use xlsfonts to list available fonts, and modify load_font\n");
printf ("in graphics.cpp.\n");
exit (1);
}
#else /* WIN32 */
LOGFONT *lf = win32_state.font_info[pointsize] = (LOGFONT*)my_malloc(sizeof(LOGFONT));
ZeroMemory(lf, sizeof(LOGFONT));
/* lfHeight specifies the desired height of characters in logical units. *
* A positive value of lfHeight will request a font that is appropriate *
* for a line spacing of lfHeight. On the other hand, setting lfHeight *
* to a negative value will obtain a font height that is compatible with *
* the desired pointsize. */
lf->lfHeight = -pointsize;
lf->lfWeight = FW_NORMAL;
lf->lfCharSet = ANSI_CHARSET;
lf->lfOutPrecision = OUT_DEFAULT_PRECIS;
lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf->lfQuality = PROOF_QUALITY;
lf->lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
strcpy(lf->lfFaceName, "Arial");
#endif
gl_state.font_is_loaded[pointsize] = true;
}
/* Return information useful for debugging.
* Used to return the top-level window object too, but that made graphics.h
* export all windows and X11 headers to the client program, so VB deleted
* that object (mainwnd) from this structure.
*/
void report_structure(t_report *report) {
report->xmult = trans_coord.wtos_xmult;
report->ymult = trans_coord.wtos_ymult;
report->xleft = trans_coord.xleft;
report->xright = trans_coord.xright;
report->ytop = trans_coord.ytop;
report->ybot = trans_coord.ybot;
report->ps_xmult = trans_coord.ps_xmult;
report->ps_ymult = trans_coord.ps_ymult;
report->top_width = trans_coord.top_width;
report->top_height = trans_coord.top_height;
}
void set_mouse_move_input (bool enable) {
gl_state.get_mouse_move_input = enable;
}
void set_keypress_input (bool enable) {
gl_state.get_keypress_input = enable;
}
void enable_or_disable_button (int ibutton, bool enabled) {
if (button_state.button[ibutton].type != BUTTON_SEPARATOR) {
button_state.button[ibutton].enabled = enabled;
#ifdef WIN32
EnableWindow(button_state.button[ibutton].hwnd, button_state.button[ibutton].enabled);
#else // X11
x11_drawbut(ibutton);
XFlush(x11_state.display);
#endif
}
}
void set_draw_mode (enum e_draw_mode draw_mode) {
/* Set normal (overwrite) or xor (useful for rubber-banding)
* drawing mode.
*/
if (draw_mode == DRAW_NORMAL) {
#ifdef X11
x11_state.current_gc = x11_state.gc_normal;
#else
if (!SetROP2(win32_state.hGraphicsDC, R2_COPYPEN))
WIN32_SELECT_ERROR();
#endif
}
else { // DRAW_XOR
#ifdef X11
x11_state.current_gc = x11_state.gc_xor;
#else
if (!SetROP2(win32_state.hGraphicsDC, R2_XORPEN))
WIN32_SELECT_ERROR();
#endif
}
gl_state.current_draw_mode = draw_mode;
}
void change_button_text(const char *button_name, const char *new_button_text) {
/* Change the text on a button with button_name to new_button_text.
* Not a strictly necessary function, since you could intead just
* destroy button_name and make a new buton.
*/
int i, bnum;
bnum = -1;
for (i=4;i<button_state.num_buttons;i++) {
if (button_state.button[i].type == BUTTON_TEXT &&
strcmp (button_state.button[i].text, button_name) == 0) {
bnum = i;
break;
}
}
if (bnum != -1) {
strncpy (button_state.button[i].text, new_button_text, BUTTON_TEXT_LEN);
#ifdef X11
x11_drawbut (i);
#else // Win32
SetWindowText(button_state.button[bnum].hwnd, new_button_text);
#endif
}
}
/**********************************
* X-Windows Specific Definitions *
*********************************/
#ifdef X11
/* Helper function called by init_graphics(). Not visible to client program. */
static void x11_init_graphics (const char *window_name, int cindex)
{
char *display_name = NULL;
int x, y; /* window position */
unsigned int border_width = 2; /* ignored by OpenWindows */
XTextProperty windowName;
/* X Windows' names for my colours. */
const char *cnames[NUM_COLOR] = {"white", "black", "grey55", "grey75", "blue",
"green", "yellow", "cyan", "red", "pink", "lightpink", "RGBi:0.0/0.39/0.0",
"magenta", "bisque", "lightskyblue", "thistle", "plum", "khaki", "coral",
"turquoise", "mediumpurple", "darkslateblue", "darkkhaki" };
XColor exact_def;
Colormap cmap;
unsigned long valuemask = 0; /* ignore XGCvalues and use defaults */
XGCValues values;
XEvent event;
/* connect to X server */
if ( (x11_state.display=XOpenDisplay(display_name)) == NULL )
{
fprintf( stderr, "Cannot connect to X server %s\n",
XDisplayName(display_name));
exit( -1 );
}
/* get screen size from display structure macro */
x11_state.screen_num = DefaultScreen(x11_state.display);
trans_coord.display_width = DisplayWidth(x11_state.display, x11_state.screen_num);
trans_coord.display_height = DisplayHeight(x11_state.display, x11_state.screen_num);
x = 0;
y = 0;
trans_coord.top_width = 2 * trans_coord.display_width / 3;
trans_coord.top_height = 4 * trans_coord.display_height / 5;
cmap = DefaultColormap(x11_state.display, x11_state.screen_num);
x11_state.private_cmap = None;
for (int i=0;i<NUM_COLOR;i++) {
if (!XParseColor(x11_state.display,cmap,cnames[i],&exact_def)) {
fprintf(stderr, "Color name %s not in database", cnames[i]);
exit(-1);
}
if (!XAllocColor(x11_state.display, cmap, &exact_def)) {
fprintf(stderr, "Couldn't allocate color %s.\n",cnames[i]);
if (x11_state.private_cmap == None) {
fprintf(stderr, "Will try to allocate a private colourmap.\n");
fprintf(stderr, "Colours will only display correctly when your "
"cursor is in the graphics window.\n"
"Exit other colour applications and rerun this "
"program if you don't like that.\n\n");
x11_state.private_cmap = XCopyColormapAndFree (x11_state.display, cmap);
cmap = x11_state.private_cmap;
if (!XAllocColor (x11_state.display, cmap, &exact_def)) {
fprintf (stderr, "Couldn't allocate color %s as private.\n",
cnames[i]);
exit (1);
}
}
else {
fprintf (stderr, "Couldn't allocate color %s as private.\n",
cnames[i]);
exit (1);
}
}
x11_state.colors[i] = exact_def.pixel;
} // End setting up colours
x11_state.toplevel = XCreateSimpleWindow(x11_state.display,
RootWindow(x11_state.display,x11_state.screen_num),
x, y, trans_coord.top_width, trans_coord.top_height,
border_width, x11_state.colors[BLACK],
x11_state.colors[cindex]);
if (x11_state.private_cmap != None)
XSetWindowColormap (x11_state.display, x11_state.toplevel, x11_state.private_cmap);
XSelectInput (x11_state.display, x11_state.toplevel, ExposureMask | StructureNotifyMask |
ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
KeyPressMask);
/* Create default Graphics Contexts. valuemask = 0 -> use defaults. */
x11_state.current_gc = x11_state.gc_normal = XCreateGC(x11_state.display, x11_state.toplevel,
valuemask, &values);
x11_state.gc_menus = XCreateGC(x11_state.display, x11_state.toplevel, valuemask, &values);
/* Create XOR graphics context for Rubber Banding */
values.function = GXxor;
values.foreground = x11_state.colors[cindex];
x11_state.gc_xor = XCreateGC(x11_state.display, x11_state.toplevel, (GCFunction | GCForeground),
&values);
/* specify font for menus. */
load_font(MENU_FONT_SIZE);
XSetFont(x11_state.display, x11_state.gc_menus, x11_state.font_info[MENU_FONT_SIZE]->fid);
/* Set drawing defaults for user-drawable area. Use whatever the *
* initial values of the current stuff was set to. */
force_setfontsize(gl_state.currentfontsize);
force_setcolor (gl_state.currentcolor);
force_setlinestyle (gl_state.currentlinestyle);
force_setlinewidth (gl_state.currentlinewidth);
// Need a non-const name to pass to XStringListTo...
// (even though X11 won't change it).
char *window_name_copy = (char *) my_malloc (BUFSIZE * sizeof (char));
strncpy (window_name_copy, window_name, BUFSIZE);
XStringListToTextProperty(&window_name_copy, 1, &windowName);
free (window_name_copy);
window_name_copy = NULL;
XSetWMName (x11_state.display, x11_state.toplevel, &windowName);
/* XSetWMIconName (x11_state.display, x11_state.toplevel, &windowName); */
/* XStringListToTextProperty copies the window_name string into *
* windowName.value. Free this memory now. */
free (windowName.value);
XMapWindow (x11_state.display, x11_state.toplevel);
x11_build_textarea ();
build_default_menu ();
/* The following is completely unnecessary if the user is using the *
* interactive (event_loop) graphics. It waits for the first Expose *
* event before returning so that I can tell the window manager has got *
* the top-level window up and running. Thus the user can start drawing *
* into this window immediately, and there's no danger of the window not *
* being ready and output being lost. */
XPeekIfEvent (x11_state.display, &event, x11_test_if_exposed, NULL);
}
/* Helper function called by event_loop(). Not visible to client program. */
static void
x11_event_loop (void (*act_on_mousebutton)(float x, float y, t_event_buttonPressed button_info),
void (*act_on_mousemove)(float x, float y),
void (*act_on_keypress)(char key_pressed),
void (*drawscreen) (void))
{
XEvent report;
int bnum;
float x, y;
#define OFF 1
#define ON 0
x11_turn_on_off (ON);
while (1) {
XNextEvent (x11_state.display, &report);
switch (report.type) {
case Expose:
if (report.xexpose.count != 0)
break;
x11_handle_expose(report, drawscreen);
break;
case ConfigureNotify:
x11_handle_configure_notify(report);
break;
case ButtonPress:
#ifdef VERBOSE
printf("Got a buttonpress.\n");
printf("Window ID is: %ld.\n",report.xbutton.window);
printf("Button pressed is: %d.\n(left click is 1; right click is 3; "
"scroll wheel click is 2; scroll wheel forward rotate is 4; "
"scroll wheel backward is 5.)\n",report.xbutton.button);
printf("Mask is: %d.\n",report.xbutton.state);
#endif
if (report.xbutton.window == x11_state.toplevel) {
x = xscrn_to_world(report.xbutton.x);
y = yscrn_to_world(report.xbutton.y);
t_event_buttonPressed button_info;
/* t_event_buttonPressed is used as a structure for storing information about a *
* button press event. This information can be passed back to and used by a client *
* program. */
x11_handle_button_info(&button_info, report.xbutton.button, report.xbutton.state);
#ifdef VERBOSE
if (button_info.shift_pressed == true)
printf("Shift is pressed at button press.\n");
if (button_info.ctrl_pressed == true)
printf("Ctrl is pressed at button press.\n");
#endif
switch (report.xbutton.button) {
case Button1: /* Left mouse click; pass back to client program */
case Button3: /* Right mouse click; pass back to client program */
/* Pass information about the button press to client program */
act_on_mousebutton (x, y, button_info);
break;
case Button2: /* Scroll wheel pressed; start panning */
panning_on(report.xbutton.x, report.xbutton.y);
break;
case Button4: /* Scroll wheel rotated forward; screen does zoom_in */
handle_zoom_in(x, y, drawscreen);
break;
case Button5: /* Scroll wheel rotated backward; screen does zoom_out */
handle_zoom_out(x, y, drawscreen);
break;
}
}
else { /* A menu button was pressed. */
bnum = x11_which_button(report.xbutton.window);
#ifdef VERBOSE
printf("Button number is %d\n",bnum);
#endif
if (button_state.button[bnum].enabled) {
button_state.button[bnum].ispressed = 1;
x11_drawbut(bnum);
XFlush(x11_state.display); /* Flash the button */
button_state.button[bnum].fcn (drawscreen);
button_state.button[bnum].ispressed = 0;
x11_drawbut(bnum);
if (button_state.button[bnum].fcn == proceed) {
x11_turn_on_off(OFF);
flushinput ();
return; /* Rather clumsy way of returning *
* control to the simulator */
}
}
}
break;
case ButtonRelease:
#ifdef VERBOSE
printf("Got a buttonrelease.\n");
printf("Window ID is: %ld.\n",report.xbutton.window);
printf("Button released is: %d.\n(left click is 1; right click is 3; "
"scroll wheel click is 2; scroll wheel forward rotate is 4; "
"scroll wheel backward is 5.)\n",report.xbutton.button);
printf("Mask is: %d.\n",report.xbutton.state);
#endif
switch (report.xbutton.button) {
case Button2: /* Scroll wheel released; stop panning */
panning_off();
break;
}
case MotionNotify:
#ifdef VERBOSE
printf("Got a MotionNotify Event.\n");
printf("x: %d y: %d\n",report.xmotion.x,report.xmotion.y);
#endif
if (pan_state.panning_enabled)
panning_execute(report.xmotion.x, report.xmotion.y, drawscreen);
else if (gl_state.get_mouse_move_input &&
report.xmotion.x <= trans_coord.top_width-MWIDTH &&
report.xmotion.y <= trans_coord.top_height-T_AREA_HEIGHT)
act_on_mousemove(xscrn_to_world(report.xmotion.x), yscrn_to_world(report.xmotion.y));
break;
case KeyPress:
#ifdef VERBOSE
printf("Got a KeyPress Event.\n");
#endif
if (gl_state.get_keypress_input)
{
char keyb_buffer[20];
XComposeStatus composestatus;
KeySym keysym;
int length, max_bytes;
max_bytes = 1;
length = XLookupString( &report.xkey, keyb_buffer, max_bytes, &keysym,
&composestatus );
keyb_buffer[length] = '\0'; /* terminating NULL */
act_on_keypress(keyb_buffer[0]);
}
break;
}
}
}
/* Creates a small window at the top of the graphics area for text messages */
static void x11_build_textarea (void)
{
XSetWindowAttributes menu_attributes;
unsigned long valuemask;
x11_state.textarea = XCreateSimpleWindow(x11_state.display,x11_state.toplevel, 0,
trans_coord.top_height-T_AREA_HEIGHT, trans_coord.display_width-MWIDTH,
T_AREA_HEIGHT, 0, x11_state.colors[BLACK], x11_state.colors[LIGHTGREY]);
menu_attributes.event_mask = ExposureMask;
/* ButtonPresses in this area are ignored. */
menu_attributes.do_not_propagate_mask = ButtonPressMask;
/* Keep text area on bottom left */
menu_attributes.win_gravity = SouthWestGravity;
valuemask = CWWinGravity | CWEventMask | CWDontPropagate;
XChangeWindowAttributes(x11_state.display, x11_state.textarea, valuemask, &menu_attributes);
XMapWindow (x11_state.display, x11_state.textarea);
}
/* Returns True if the event passed in is an exposure event. Note that
* the bool type returned by this function is defined in Xlib.h.
*/
static Bool x11_test_if_exposed (Display *disp, XEvent *event_ptr, XPointer dummy)
{
if (event_ptr->type == Expose) {
return (True);
}
return (False);
}
/* draws text center at xc, yc -- used only by menu drawing stuff */
static void menutext(Window win, int xc, int yc, const char *text)
{
int len, width;
len = strlen(text);
width = XTextWidth(x11_state.font_info[MENU_FONT_SIZE], text, len);
XDrawString(x11_state.display, win, x11_state.gc_menus, xc-width/2,
yc + (x11_state.font_info[MENU_FONT_SIZE]->ascent
- x11_state.font_info[MENU_FONT_SIZE]->descent)/2,
text, len);
}
/* Draws button bnum in either its pressed or unpressed state. */
static void x11_drawbut (int bnum)
{
int width, height, thick, i, ispressed;
XPoint mypoly[6];
width = button_state.button[bnum].width;
height = button_state.button[bnum].height;
if (button_state.button[bnum].type == BUTTON_SEPARATOR) {
int x,y;
x = button_state.button[bnum].xleft;
y = button_state.button[bnum].ytop;
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[WHITE]);
XDrawLine(x11_state.display, x11_state.menu, x11_state.gc_menus, x, y+1, x+width, y+1);
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
XDrawLine(x11_state.display, x11_state.menu, x11_state.gc_menus, x, y, x+width, y);
return;
}
ispressed = button_state.button[bnum].ispressed;
thick = 2;
/* Draw top and left edges of 3D box. */
if (ispressed) {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
}
else {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[WHITE]);
}
/* Note: X Windows doesn't appear to draw the bottom pixel of *
* a polygon with XFillPolygon, so I make this 1 pixel thicker *
* to compensate. */
mypoly[0].x = 0;
mypoly[0].y = height;
mypoly[1].x = 0;
mypoly[1].y = 0;
mypoly[2].x = width;
mypoly[2].y = 0;
mypoly[3].x = width-thick;
mypoly[3].y = thick;
mypoly[4].x = thick;
mypoly[4].y = thick;
mypoly[5].x = thick;
mypoly[5].y = height-thick;
XFillPolygon(x11_state.display,button_state.button[bnum].win,x11_state.gc_menus,mypoly,6,Convex,
CoordModeOrigin);
/* Draw bottom and right edges of 3D box. */
if (ispressed) {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[WHITE]);
}
else {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
}
mypoly[0].x = 0;
mypoly[0].y = height;
mypoly[1].x = width;
mypoly[1].y = height;
mypoly[2].x = width;
mypoly[2].y = 0;
mypoly[3].x = width-thick;
mypoly[3].y = thick;
mypoly[4].x = width-thick;
mypoly[4].y = height-thick;
mypoly[5].x = thick;
mypoly[5].y = height-thick;
XFillPolygon(x11_state.display,button_state.button[bnum].win,x11_state.gc_menus,mypoly,6,Convex,
CoordModeOrigin);
/* Draw background */
if (ispressed) {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[DARKGREY]);
}
else {
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[LIGHTGREY]);
}
/* Give x,y of top corner and width and height */
XFillRectangle (x11_state.display,button_state.button[bnum].win,x11_state.gc_menus,thick,thick,
width-2*thick, height-2*thick);
/* Draw polygon, if there is one */
if (button_state.button[bnum].type == BUTTON_POLY) {
for (i=0;i<3;i++) {
mypoly[i].x = button_state.button[bnum].poly[i][0];
mypoly[i].y = button_state.button[bnum].poly[i][1];
}
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
XFillPolygon(x11_state.display,button_state.button[bnum].win,
x11_state.gc_menus,mypoly,3,Convex,CoordModeOrigin);
}
/* Draw text, if there is any */
if (button_state.button[bnum].type == BUTTON_TEXT) {
if (button_state.button[bnum].enabled)
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
else
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[DARKGREY]);
menutext(button_state.button[bnum].win,button_state.button[bnum].width/2,
button_state.button[bnum].height/2,button_state.button[bnum].text);
}
}
static int x11_which_button (Window win)
{
int i;
for (i=0;i<button_state.num_buttons;i++) {
if (button_state.button[i].win == win)
return(i);
}
printf("Error: Unknown button ID in which_button.\n");
return(0);
}
/* Shows when the menu is active or inactive by colouring the
* buttons.
*/
static void x11_turn_on_off (int pressed)
{
int i;
for (i=0;i<button_state.num_buttons;i++) {
button_state.button[i].ispressed = pressed;
x11_drawbut(i);
}
}
static void x11_drawmenu(void)
{
int i;
XClearWindow (x11_state.display, x11_state.menu);
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[WHITE]);
XDrawRectangle(x11_state.display, x11_state.menu, x11_state.gc_menus, 0, 0, MWIDTH,
trans_coord.top_height);
XSetForeground(x11_state.display, x11_state.gc_menus,x11_state.colors[BLACK]);
XDrawLine(x11_state.display, x11_state.menu, x11_state.gc_menus, 0, trans_coord.top_height-1,
MWIDTH, trans_coord.top_height-1);
XDrawLine(x11_state.display, x11_state.menu, x11_state.gc_menus, MWIDTH-1,
trans_coord.top_height, MWIDTH-1, 0);
for (i=0;i<button_state.num_buttons;i++) {
x11_drawbut(i);
}
}
static void x11_handle_expose(XEvent report, void (*drawscreen) (void))
{
#ifdef VERBOSE
printf("Got an expose event.\n");
printf("Count is: %d.\n",report.xexpose.count);
printf("Window ID is: %ld.\n",report.xexpose.window);
#endif
if (report.xexpose.window == x11_state.menu)
x11_drawmenu();
else if (report.xexpose.window == x11_state.toplevel)
drawscreen();
else if (report.xexpose.window == x11_state.textarea)
draw_message();
}
static void x11_handle_configure_notify(XEvent report)
{
trans_coord.top_width = report.xconfigure.width;
trans_coord.top_height = report.xconfigure.height;
update_transform();
x11_drawmenu();
draw_message();
#ifdef VERBOSE
printf("Got a ConfigureNotify.\n");
printf("New width: %d New height: %d.\n",trans_coord.top_width,trans_coord.top_height);
#endif
}
static void x11_handle_button_info (t_event_buttonPressed *button_info,
int buttonNumber, int Xbutton_state)
{
button_info->button = buttonNumber;
if (Xbutton_state & 1)
button_info->shift_pressed = true;
else
button_info->shift_pressed = false;
if (Xbutton_state & 4)
button_info->ctrl_pressed = true;
else
button_info->ctrl_pressed = false;
}
#endif /* X-Windows Specific Definitions */
/*************************************************
* Microsoft Windows (WIN32) Specific Definitions *
*************************************************/
#ifdef WIN32
static void
win32_init_graphics (const char *window_name, int cindex)
{
WNDCLASS wndclass;
HINSTANCE hInstance = GetModuleHandle(NULL);
int x, y;
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = win32_colors[gl_state.currentcolor];
lb.lbHatch = (LONG)NULL;
x = 0;
y = 0;
/* get screen size from display structure macro */
trans_coord.display_width = GetSystemMetrics( SM_CXSCREEN );
if (!(trans_coord.display_width))
WIN32_CREATE_ERROR();
trans_coord.display_height = GetSystemMetrics( SM_CYSCREEN );
if (!(trans_coord.display_height))
WIN32_CREATE_ERROR();
trans_coord.top_width = 2*trans_coord.display_width/3;
trans_coord.top_height = 4*trans_coord.display_height/5;
/* Grab the Application name */
wsprintf(szAppName, TEXT(window_name));
//win32_state.hGraphicsPen = CreatePen(win32_line_styles[SOLID], 1, win32_colors[BLACK]);
win32_state.hGraphicsPen = ExtCreatePen(PS_GEOMETRIC | win32_line_styles[gl_state.currentlinestyle]
| PS_ENDCAP_FLAT, 1, &lb, (LONG)NULL, NULL);
if(!win32_state.hGraphicsPen)
WIN32_CREATE_ERROR();
win32_state.hGraphicsBrush = CreateSolidBrush(win32_colors[DARKGREY]);
if(!win32_state.hGraphicsBrush)
WIN32_CREATE_ERROR();
win32_state.hGrayBrush = CreateSolidBrush(win32_colors[LIGHTGREY]);
if(!win32_state.hGrayBrush)
WIN32_CREATE_ERROR();
load_font (gl_state.currentfontsize);
win32_state.hGraphicsFont = CreateFontIndirect(win32_state.font_info[gl_state.currentfontsize]);
if (!win32_state.hGraphicsFont)
WIN32_CREATE_ERROR();
/* Register the Main Window class */
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wndclass.lpfnWndProc = WIN32_MainWND;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor( NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) CreateSolidBrush(win32_colors[cindex]);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass)) {
printf ("Error code: %d\n", GetLastError());
MessageBox(NULL, TEXT("Initialization of Windows graphics (init_graphics) failed."),
szAppName, MB_ICONERROR);
exit(-1);
}
/* Register the Graphics Window class */
wndclass.lpfnWndProc = WIN32_GraphicsWND;
wndclass.hIcon = NULL;
wndclass.lpszClassName = szGraphicsName;
if(!RegisterClass(&wndclass))
WIN32_DRAW_ERROR();
/* Register the Status Window class */
wndclass.lpfnWndProc = WIN32_StatusWND;
wndclass.hIcon = NULL;
wndclass.lpszClassName = szStatusName;
wndclass.hbrBackground = win32_state.hGrayBrush;
if(!RegisterClass(&wndclass))
WIN32_DRAW_ERROR();
/* Register the Buttons Window class */
wndclass.lpfnWndProc = WIN32_ButtonsWND;
wndclass.hIcon = NULL;
wndclass.lpszClassName = szButtonsName;
wndclass.hbrBackground = win32_state.hGrayBrush;
if (!RegisterClass(&wndclass))
WIN32_DRAW_ERROR();
win32_state.hMainWnd = CreateWindow(szAppName, TEXT(window_name),
WS_OVERLAPPEDWINDOW, x, y, trans_coord.top_width,
trans_coord.top_height, NULL, NULL, hInstance, NULL);
if(!win32_state.hMainWnd)
WIN32_DRAW_ERROR();
/* Set drawing defaults for user-drawable area. Use whatever the *
* initial values of the current stuff was set to. */
if (ShowWindow(win32_state.hMainWnd, SW_SHOWNORMAL))
WIN32_DRAW_ERROR();
build_default_menu();
if (!UpdateWindow(win32_state.hMainWnd))
WIN32_DRAW_ERROR();
win32_drain_message_queue ();
}
static LRESULT CALLBACK
WIN32_MainWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
MINMAXINFO FAR *lpMinMaxInfo;
switch(message)
{
case WM_CREATE:
win32_state.hStatusWnd = CreateWindow(szStatusName, NULL, WS_CHILDWINDOW | WS_VISIBLE,
0, 0, 0, 0, hwnd, (HMENU) 102, (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE), NULL);
win32_state.hButtonsWnd = CreateWindow(szButtonsName, NULL, WS_CHILDWINDOW | WS_VISIBLE,
0, 0, 0, 0, hwnd, (HMENU) 103, (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE), NULL);
win32_state.hGraphicsWnd = CreateWindow(szGraphicsName, NULL, WS_CHILDWINDOW | WS_VISIBLE,
0, 0, 0, 0, hwnd, (HMENU) 101, (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE), NULL);
return 0;
case WM_SIZE:
/* Window has been resized. Save the new client dimensions */
trans_coord.top_width = LOWORD (lParam);
trans_coord.top_height = HIWORD (lParam);
/* Resize the children windows */
if(!MoveWindow(win32_state.hGraphicsWnd, 1, 1, trans_coord.top_width - MWIDTH - 1,
trans_coord.top_height - T_AREA_HEIGHT - 1, TRUE))
WIN32_DRAW_ERROR();
if(!MoveWindow(win32_state.hStatusWnd, 0, trans_coord.top_height - T_AREA_HEIGHT,
trans_coord.top_width - MWIDTH, T_AREA_HEIGHT, TRUE))
WIN32_DRAW_ERROR();
if(!MoveWindow(win32_state.hButtonsWnd, trans_coord.top_width - MWIDTH, 0, MWIDTH,
trans_coord.top_height, TRUE))
WIN32_DRAW_ERROR();
return 0;
// WC : added to solve window resizing problem
case WM_GETMINMAXINFO:
// set the MINMAXINFO structure pointer
lpMinMaxInfo = (MINMAXINFO FAR *) lParam;
lpMinMaxInfo->ptMinTrackSize.x = trans_coord.display_width / 2;
lpMinMaxInfo->ptMinTrackSize.y = trans_coord.display_height / 2;
return 0;
case WM_DESTROY:
if(!DeleteObject(win32_state.hGrayBrush))
WIN32_DELETE_ERROR();
PostQuitMessage(0);
return 0;
case WM_KEYDOWN:
if (gl_state.get_keypress_input)
win32_keypress_ptr((char) wParam);
return 0;
// Controls graphics: does zoom in or zoom out depending on direction of mousewheel scrolling.
// Only the window with the input focus will receive this message. In our case, the top-level
// window has the input focus, thus the code will not work if put in WIN32_GraphicsWND.
case WM_MOUSEWHEEL:
// t_event_buttonPressed is used as a structure for storing information about a mouse
// button press event. This information can be passed back to and used by a client
// program.
t_event_buttonPressed button_info;
win32_handle_button_info(button_info, message, wParam);
win32_handle_mousewheel_zooming(wParam, lParam);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static LRESULT CALLBACK
WIN32_GraphicsWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static TEXTMETRIC tm;
PAINTSTRUCT ps;
static RECT oldAdjustRect;
static HPEN hDotPen = 0;
static int X, Y, i;
switch(message)
{
case WM_CREATE:
/* Get the text metrics once upon creation (system font cannot change) */
win32_state.hGraphicsDC = GetDC (hwnd);
if(!win32_state.hGraphicsDC)
WIN32_DRAW_ERROR();
if(!SetBkMode(win32_state.hGraphicsDC, TRANSPARENT))
WIN32_DRAW_ERROR();
/* Setup the pens, etc */
gl_state.currentlinestyle = SOLID;
gl_state.currentcolor = BLACK;
gl_state.currentlinewidth = 1;
gl_state.currentfontsize = 12;
return 0;
case WM_PAINT:
win32_GraphicsWND_handle_WM_PAINT(hwnd, ps, hDotPen, oldAdjustRect);
return 0;
case WM_SIZE:
/* Window has been resized. New client area dimensions can be retrieved from
* lParam using LOWORD() and HIWORD() macros.
*/
update_transform();
return 0;
case WM_DESTROY:
if(!DeleteObject(win32_state.hGraphicsPen))
WIN32_DELETE_ERROR();
if(!DeleteObject(win32_state.hGraphicsBrush))
WIN32_DELETE_ERROR();
if(!DeleteObject(win32_state.hGraphicsFont))
WIN32_DELETE_ERROR();
PostQuitMessage(0);
return 0;
// left click and right click have the same functionality
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
win32_GraphicsWND_handle_WM_LRBUTTONDOWN(message, wParam, lParam, X, Y, oldAdjustRect);
return 0;
// If the mouse device has a scroll wheel, then this is a click of the wheel.
// Enable panning by holding down the mouse wheel and drag.
case WM_MBUTTONDOWN:
win32_GraphicsWND_handle_WM_MBUTTONDOWN(hwnd, message, wParam, lParam);
return 0;
// Release the middle mouse button (mouse wheel) to stop panning
case WM_MBUTTONUP:
// turn off panning_enabled
panning_off();
/* Stops the mouse capturing started by SetCapture(). */
ReleaseCapture();
return 0;
case WM_MOUSEMOVE:
win32_GraphicsWND_handle_WM_MOUSEMOVE(lParam, X, Y, oldAdjustRect);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static void
win32_GraphicsWND_handle_WM_PAINT(HWND hwnd, PAINTSTRUCT &ps, HPEN &hDotPen, RECT &oldAdjustRect)
{
// was in xor mode, but got a general redraw.
// switch to normal drawing so we repaint properly.
if (gl_state.current_draw_mode == DRAW_XOR) {
set_draw_mode(DRAW_NORMAL);
win32_invalidate_screen();
return;
}
BeginPaint(hwnd, &ps);
if(!win32_state.hGraphicsDC)
WIN32_DRAW_ERROR();
if (win32_state.InEventLoop) {
// if program was still executing the "Window" command and drawing rubber band
if(win32_state.windowAdjustFlag == WAITING_FOR_SECOND_CORNER_POINT) {
/* ps.rcPaint specifies the screen coordinates of the window's client area
* in which drawing is requested. This information is used to indicate that
* the application window has been minimized and then restored. If so, need
* to redraw the screen before drawing new rubber band.
*/
if (ps.rcPaint.right == (trans_coord.top_width-MWIDTH-1)
&& ps.rcPaint.bottom == (trans_coord.top_height-T_AREA_HEIGHT-1))
win32_drawscreen_ptr();
// Create pen for rubber band drawing
hDotPen = CreatePen(PS_DASH, 1, win32_colors[gl_state.background_cindex]);
if(!hDotPen)
WIN32_CREATE_ERROR();
if (!SetROP2(win32_state.hGraphicsDC, R2_XORPEN))
WIN32_SELECT_ERROR();
if(!SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_BRUSH)))
WIN32_SELECT_ERROR();
if(!SelectObject(win32_state.hGraphicsDC, hDotPen))
WIN32_SELECT_ERROR();
// Don't need to erase old rubber band if the window has been minimized and
// restored, because previous drawings were invalidated when the window was
// minimized.
if (ps.rcPaint.right != (trans_coord.top_width-MWIDTH-1)
|| ps.rcPaint.bottom != (trans_coord.top_height-T_AREA_HEIGHT-1))
{
// Erase old rubber band before drawing a new one
if(!Rectangle(win32_state.hGraphicsDC, oldAdjustRect.left, oldAdjustRect.top,
oldAdjustRect.right, oldAdjustRect.bottom))
WIN32_DRAW_ERROR();
}
// Draw new rubber band
if(!Rectangle(win32_state.hGraphicsDC, win32_state.adjustRect.left,
win32_state.adjustRect.top, win32_state.adjustRect.right,
win32_state.adjustRect.bottom))
WIN32_DRAW_ERROR();
oldAdjustRect = win32_state.adjustRect;
if (!SetROP2(win32_state.hGraphicsDC, R2_COPYPEN))
WIN32_SELECT_ERROR();
if(!SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_PEN)))
WIN32_SELECT_ERROR();
if(!DeleteObject(hDotPen))
WIN32_DELETE_ERROR();
}
else {
win32_drawscreen_ptr();
}
}
EndPaint(hwnd, &ps);
/* Crash hard if called at wrong time */
/* win32_state.hGraphicsDC = NULL;*/
}
static void win32_GraphicsWND_handle_WM_LRBUTTONDOWN(UINT message, WPARAM wParam, LPARAM lParam,
int &X, int &Y, RECT &oldAdjustRect)
{
// t_event_buttonPressed is used as a structure for storing information about a mouse
// button press event. This information can be passed back to and used by a client
// program.
t_event_buttonPressed button_info;
win32_handle_button_info(button_info, message, wParam);
if (win32_state.windowAdjustFlag == WINDOW_DEACTIVATED) {
// Call function in client program
win32_mouseclick_ptr(xscrn_to_world(LOWORD(lParam)), yscrn_to_world(HIWORD(lParam)),
button_info);
}
else {
// Special handling for the "Window" command, which takes multiple clicks.
// First you push the button, then you click for one corner, then you click for the other
// corner.
if(win32_state.windowAdjustFlag == WAITING_FOR_FIRST_CORNER_POINT) {
win32_state.windowAdjustFlag = WAITING_FOR_SECOND_CORNER_POINT;
X = win32_state.adjustRect.left = win32_state.adjustRect.right = LOWORD(lParam);
Y = win32_state.adjustRect.top = win32_state.adjustRect.bottom = HIWORD(lParam);
oldAdjustRect = win32_state.adjustRect;
}
else {
int i;
int adjustx[2], adjusty[2];
win32_state.windowAdjustFlag = WINDOW_DEACTIVATED;
button_state.button[win32_state.adjustButton].ispressed = 0;
SendMessage(button_state.button[win32_state.adjustButton].hwnd, BM_SETSTATE, 0, 0);
for (i=0; i<button_state.num_buttons; i++) {
if (button_state.button[i].type != BUTTON_SEPARATOR
&& button_state.button[i].enabled)
{
if(!EnableWindow (button_state.button[i].hwnd, TRUE))
WIN32_DRAW_ERROR();
}
}
adjustx[0] = win32_state.adjustRect.left;
adjustx[1] = win32_state.adjustRect.right;
adjusty[0] = win32_state.adjustRect.top;
adjusty[1] = win32_state.adjustRect.bottom;
update_win(adjustx, adjusty, win32_invalidate_screen);
}
}
}
static void
win32_GraphicsWND_handle_WM_MBUTTONDOWN(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// t_event_buttonPressed is used as a structure for storing information about a mouse
// button press event. This information can be passed back to and used by a client
// program.
t_event_buttonPressed button_info;
win32_handle_button_info(button_info, message, wParam);
// get x- and y- coordinates of the cursor. Do not use LOWORD or HIWORD macros
// to extract the coordinates because these macros can return incorrect results
// on systems with multiple monitors.
int xPos, yPos;
xPos = GET_X_LPARAM(lParam);
yPos = GET_Y_LPARAM(lParam);
// turn on panning_enabled
panning_on(xPos, yPos);
/* Windows function specifically designed for mouse click and drag.
* This function sends all mouse message to hGraphicsWnd, even if
* the cursor is outside the client program's top-level window.
*/
SetCapture(hwnd);
}
static void
win32_GraphicsWND_handle_WM_MOUSEMOVE(LPARAM lParam, int &X, int &Y, RECT &oldAdjustRect)
{
#ifdef VERBOSE
printf("Got a MotionNotify Event.\n");
printf("x: %d y: %d\n",GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
#endif
if(win32_state.windowAdjustFlag == WAITING_FOR_FIRST_CORNER_POINT) {
return;
}
else if (win32_state.windowAdjustFlag == WAITING_FOR_SECOND_CORNER_POINT) {
if ( X > LOWORD(lParam)) {
win32_state.adjustRect.left = LOWORD(lParam);
win32_state.adjustRect.right = X;
}
else {
win32_state.adjustRect.left = X;
win32_state.adjustRect.right = LOWORD(lParam);
}
if ( Y > HIWORD(lParam)) {
win32_state.adjustRect.top = HIWORD(lParam);
win32_state.adjustRect.bottom = Y;
}
else {
win32_state.adjustRect.top = Y;
win32_state.adjustRect.bottom = HIWORD(lParam);
}
if(!InvalidateRect(win32_state.hGraphicsWnd, &oldAdjustRect, FALSE))
WIN32_DRAW_ERROR();
if(!InvalidateRect(win32_state.hGraphicsWnd, &win32_state.adjustRect, FALSE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hGraphicsWnd))
WIN32_DRAW_ERROR();
return;
}
else if (pan_state.panning_enabled) {
// get x- and y- coordinates of the cursor. Do not use LOWORD or HIWORD macros
// to extract the coordinates because these macros can return incorrect results
// on systems with multiple monitors.
int xPos, yPos;
xPos = GET_X_LPARAM(lParam);
yPos = GET_Y_LPARAM(lParam);
panning_execute(xPos, yPos, win32_drawscreen_ptr);
}
else if (gl_state.get_mouse_move_input) {
win32_mousemove_ptr(xscrn_to_world(LOWORD(lParam)), yscrn_to_world(HIWORD(lParam)));
}
}
static LRESULT CALLBACK
WIN32_StatusWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch(message)
{
case WM_CREATE:
hdc = GetDC(hwnd);
if(!hdc)
WIN32_DRAW_ERROR();
if(!SetBkMode(hdc, TRANSPARENT))
WIN32_DRAW_ERROR();
if(!ReleaseDC(hwnd, hdc))
WIN32_DRAW_ERROR();
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
if(!hdc)
WIN32_DRAW_ERROR();
if(!GetClientRect(hwnd, &rect))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(NULL_BRUSH)))
WIN32_SELECT_ERROR();
if(!SelectObject(hdc, GetStockObject(WHITE_PEN)))
WIN32_SELECT_ERROR();
if(!Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(BLACK_PEN)))
WIN32_SELECT_ERROR();
if(!MoveToEx(hdc, rect.left, rect.bottom-1, NULL))
WIN32_DRAW_ERROR();
if(!LineTo(hdc, rect.right-1, rect.bottom-1))
WIN32_DRAW_ERROR();
if(!LineTo(hdc, rect.right-1, rect.top))
WIN32_DRAW_ERROR();
if(!DrawText(hdc, TEXT(gl_state.statusMessage), -1, &rect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE))
WIN32_DRAW_ERROR();
if(!EndPaint(hwnd, &ps))
WIN32_DRAW_ERROR();
return 0;
case WM_SIZE:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static LRESULT CALLBACK
WIN32_ButtonsWND(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static HBRUSH hBrush;
int i;
switch(message)
{
case WM_COMMAND:
if (win32_state.windowAdjustFlag == WINDOW_DEACTIVATED) {
button_state.button[LOWORD(wParam) - 200].fcn(win32_invalidate_screen);
if (win32_state.windowAdjustFlag != WINDOW_DEACTIVATED) {
win32_state.adjustButton = LOWORD(wParam) - 200;
button_state.button[win32_state.adjustButton].ispressed = 1;
for (i=0; i<button_state.num_buttons; i++) {
EnableWindow(button_state.button[i].hwnd, FALSE);
SendMessage(button_state.button[i].hwnd, BM_SETSTATE,
button_state.button[i].ispressed, 0);
}
}
}
SetFocus(win32_state.hMainWnd);
return 0;
case WM_CREATE:
hdc = GetDC(hwnd);
if(!hdc)
WIN32_DRAW_ERROR();
hBrush = CreateSolidBrush(win32_colors[LIGHTGREY]);
if(!hBrush)
WIN32_CREATE_ERROR();
if(!SelectObject(hdc, hBrush))
WIN32_SELECT_ERROR();
if(!SetBkMode(hdc, TRANSPARENT))
WIN32_DRAW_ERROR();
if(!ReleaseDC(hwnd, hdc))
WIN32_DRAW_ERROR();
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
if(!hdc)
WIN32_DRAW_ERROR();
if(!GetClientRect(hwnd, &rect))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(NULL_BRUSH)))
WIN32_SELECT_ERROR();
if(!SelectObject(hdc, GetStockObject(WHITE_PEN)))
WIN32_SELECT_ERROR();
if(!Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(BLACK_PEN)))
WIN32_SELECT_ERROR();
if(!MoveToEx(hdc, rect.left, rect.bottom-1, NULL))
WIN32_DRAW_ERROR();
if(!LineTo(hdc, rect.right-1, rect.bottom-1))
WIN32_DRAW_ERROR();
if(!LineTo(hdc, rect.right-1, rect.top))
WIN32_DRAW_ERROR();
for (i=0; i < button_state.num_buttons; i++) {
if(button_state.button[i].type == BUTTON_SEPARATOR) {
int x, y, w;
x = button_state.button[i].xleft;
y = button_state.button[i].ytop;
w = button_state.button[i].width;
if(!MoveToEx (hdc, x, y, NULL))
WIN32_DRAW_ERROR();
if(!LineTo (hdc, x + w, y))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(WHITE_PEN)))
WIN32_SELECT_ERROR();
if(!MoveToEx (hdc, x, y+1, NULL))
WIN32_DRAW_ERROR();
if(!LineTo (hdc, x + w, y+1))
WIN32_DRAW_ERROR();
if(!SelectObject(hdc, GetStockObject(BLACK_PEN)))
WIN32_SELECT_ERROR();
}
}
if(!EndPaint(hwnd, &ps))
WIN32_DRAW_ERROR();
return 0;
case WM_DESTROY:
for (i=0; i<button_state.num_buttons; i++) {
}
if(!DeleteObject(hBrush))
WIN32_DELETE_ERROR();
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
static void WIN32_SELECT_ERROR()
{
char msg[BUFSIZE];
sprintf (msg, "Error %i: Couldn't select graphics object on line %d of graphics.c\n",
GetLastError(), __LINE__);
MessageBox(NULL, msg, NULL, MB_OK);
exit(-1);
}
static void WIN32_DELETE_ERROR()
{
char msg[BUFSIZE];
sprintf (msg, "Error %i: Couldn't delete graphics object on line %d of graphics.c\n",
GetLastError(), __LINE__);
MessageBox(NULL, msg, NULL, MB_OK);
exit(-1);
}
static void WIN32_CREATE_ERROR()
{
char msg[BUFSIZE];
sprintf (msg, "Error %i: Couldn't create graphics object on line %d of graphics.c\n",
GetLastError(), __LINE__);
MessageBox(NULL, msg, NULL, MB_OK);
exit(-1);
}
static void WIN32_DRAW_ERROR()
{
char msg[BUFSIZE];
sprintf (msg, "Error %i: Couldn't draw graphics object on line %d of graphics.c\n",
GetLastError(), __LINE__);
MessageBox(NULL, msg, NULL, MB_OK);
exit(-1);
}
static void win32_invalidate_screen(void)
{
/* Tells the graphics engine to redraw the graphics display since information has changed */
if(!InvalidateRect(win32_state.hGraphicsWnd, NULL, FALSE))
WIN32_DRAW_ERROR();
if(!UpdateWindow(win32_state.hGraphicsWnd))
WIN32_DRAW_ERROR();
}
static void win32_reset_state () {
// Not sure exactly what needs to be reset to NULL etc.
// Resetting everthing to be safe.
win32_state.hGraphicsPen = 0;
win32_state.hGraphicsBrush = 0;
win32_state.hGrayBrush = 0;
win32_state.hGraphicsDC = 0;
win32_state.hGraphicsFont = 0;
/* These are used for the "Window" graphics button. They keep track of whether we're entering
* the window rectangle to zoom to, etc.
*/
win32_state.windowAdjustFlag = WINDOW_DEACTIVATED;
win32_state.adjustButton = -1;
win32_state.InEventLoop = false;
}
static void win32_drain_message_queue () {
// Drain the message queue, so we don't have a quit message lying around
// that will stop us from re-opening a window later if desired.
MSG msg;
while (PeekMessage(&msg, win32_state.hMainWnd, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
printf ("Got the quit message.\n");
}
}
}
static void win32_handle_mousewheel_zooming(WPARAM wParam, LPARAM lParam)
{
// zDelta indicates the distance which the mouse wheel is rotated.
// The value for zDelta is a multiple of WHEEL_DELTA, which is 120.
// WHEEL_DELTA is the value for scrolling the mouse wheel by one increment.
short zDelta;
zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// roll_detent captures how many increments the mouse wheel has been scrolled by.
int roll_detent;
roll_detent = zDelta/WHEEL_DELTA;
// roll_detent will be negative if wheel was roatated backward, toward the user.
if (roll_detent <0)
roll_detent *= -1;
// get x- and y- coordinates of the cursor. WM_MOUSEWHEEL receives coordinates of
// the cursor relative to the upper-left corner of the screen instead of the client
// program. Therefore, call ScreenToClient() to convert.
POINT mousePos;
mousePos.x = GET_X_LPARAM(lParam);
mousePos.y = GET_Y_LPARAM(lParam);
ScreenToClient(win32_state.hMainWnd, &mousePos);
int i;
for (i=0; i<roll_detent; i++) {
// Positive value for zDelta indicates that the wheel was rotated forward, which
// will trigger zoom_in. Otherwise, zoom_out is called.
if (zDelta > 0)
handle_zoom_in(xscrn_to_world(mousePos.x), yscrn_to_world(mousePos.y),
win32_drawscreen_ptr);
else
handle_zoom_out(xscrn_to_world(mousePos.x), yscrn_to_world(mousePos.y),
win32_drawscreen_ptr);
}
}
static void win32_handle_button_info (t_event_buttonPressed &button_info,
UINT message, WPARAM wParam)
{
/* The parameter "wParam" is an unsigned int. In this case, it contains information indicating *
* whether various virtual keys (ie. modifier keys) are held during a mouse button press *
* event. */
if (wParam & MK_SHIFT)
button_info.shift_pressed = true;
else
button_info.shift_pressed = false;
if (wParam & MK_CONTROL)
button_info.ctrl_pressed = true;
else
button_info.ctrl_pressed = false;
/* Parameter "message" indicates what button is pressed: pass 1 for left click,
* 3 for right click, 2 for scroll wheel click, 4 for scroll wheel forward rotate,
* and 5 for scroll wheel backward rotate.
* We follow this convention in order to be consistent for both X11 and WIN32.
*/
switch (message)
{
case (WM_LBUTTONDOWN):
button_info.button = 1;
break;
case (WM_RBUTTONDOWN):
button_info.button = 3;
break;
case (WM_MBUTTONDOWN):
button_info.button = 2;
break;
case (WM_MOUSEWHEEL):
short zDelta;
zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// Positive value for zDelta indicates that the wheel was rotated forward,
// away from user, and negative value indicates wheel rotated backward.
if (zDelta > 0)
button_info.button = 4;
else
button_info.button = 5;
break;
}
#ifdef VERBOSE
printf("Button pressed is: %d.\n(left click is 1; right click is 3; "
"scroll wheel click is 2; scroll wheel forward rotate is 4; "
"scroll wheel backward is 5.)\n",button_info.button);
if (button_info.shift_pressed == true)
printf("Shift is pressed at button press.\n");
if (button_info.ctrl_pressed == true)
printf("Ctrl is pressed at button press.\n");
#endif
}
static void _drawcurve(t_point *points, int npoints, int fill) {
/* Draw a beizer curve.
* Must have 3I+1 points, since each Beizer curve needs 3 points and we also
* need an initial starting point
*/
float xmin, ymin, xmax, ymax;
int i;
if ((npoints - 1) % 3 != 0 || npoints > MAXPTS)
WIN32_DRAW_ERROR();
/* Conservative (but fast) clip test -- check containing rectangle of *
* polygon. */
xmin = xmax = points[0].x;
ymin = ymax = points[0].y;
for (i=1;i<npoints;i++) {
xmin = min (xmin,points[i].x);
xmax = max (xmax,points[i].x);
ymin = min (ymin,points[i].y);
ymax = max (ymax,points[i].y);
}
if (rect_off_screen(xmin,ymin,xmax,ymax))
return;
if (gl_state.disp_type == SCREEN) {
#ifdef X11
/* implement X11 version here */
#else /* Win32 */
// create POINT array
HPEN hOldPen;
POINT pts[MAXPTS];
int i;
for (i = 0; i < npoints; i++) {
pts[i].x = xworld_to_scrn(points[i].x);
pts[i].y = yworld_to_scrn(points[i].y);
}
if (fill) {
/* NULL_PEN is a Windows stock object which does not draw anything. Set current *
* pen to NULL_PEN in order to fill the curve without drawing the outline. */
hOldPen = (HPEN)SelectObject(win32_state.hGraphicsDC, GetStockObject(NULL_PEN));
if(!(hOldPen))
WIN32_SELECT_ERROR();
}
if (!BeginPath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
if(!PolyBezier(win32_state.hGraphicsDC, pts, npoints))
WIN32_DRAW_ERROR();
if (!EndPath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
if (!fill) {
if (!StrokePath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
}
else {
if (!FillPath(win32_state.hGraphicsDC))
WIN32_DRAW_ERROR();
}
if (fill) {
/* Need to restore the original pen into the device context after filling. */
if(!SelectObject(win32_state.hGraphicsDC, hOldPen))
WIN32_SELECT_ERROR();
}
#endif
}
else {
int i;
fprintf(gl_state.ps, "newpath\n");
fprintf(gl_state.ps, "%.2f %.2f moveto\n", xworld_to_post(points[0].x),
yworld_to_post(points[0].y));
for (i = 1; i < npoints; i+= 3)
fprintf(gl_state.ps,"%.2f %.2f %.2f %.2f %.2f %.2f curveto\n",
xworld_to_post(points[i].x), yworld_to_post(points[i].y),
xworld_to_post(points[i+1].x), yworld_to_post(points[i+1].y),
xworld_to_post(points[i+2].x), yworld_to_post(points[i+2].y));
if (!fill)
fprintf(gl_state.ps, "stroke\n");
else
fprintf(gl_state.ps, "fill\n");
}
}
void win32_drawcurve(t_point *points,
int npoints) {
_drawcurve(points, npoints, 0);
}
void win32_fillcurve(t_point *points,
int npoints) {
_drawcurve(points, npoints, 1);
}
#endif /******** Win32 Specific Definitions ********************/
#else /***** NO_GRAPHICS *******/
/* No graphics at all. Stub everything out so calling program doesn't have to change
* but of course graphics won't do anything.
*/
#include "graphics.h"
void event_loop (void (*act_on_mousebutton) (float x, float y, t_event_buttonPressed button_info),
void (*act_on_mousemove) (float x, float y),
void (*act_on_keypress) (char key_pressed),
void (*drawscreen) (void)) { }
void init_graphics (const char *window_name, int cindex) { }
void close_graphics (void) { }
void update_message (const char *msg) { }
void draw_message (void) { }
void init_world (float xl, float yt, float xr, float yb) { }
void flushinput (void) { }
void setcolor (int cindex) { }
int getcolor (void) { return 0; }
void setlinestyle (int linestyle) { }
void setlinewidth (int linewidth) { }
void setfontsize (int pointsize) { }
void drawline (float x1, float y1, float x2, float y2) { }
void drawrect (float x1, float y1, float x2, float y2) { }
void fillrect (float x1, float y1, float x2, float y2) { }
void fillpoly (t_point *points, int npoints) { }
void drawarc (float xcen, float ycen, float rad, float startang,
float angextent) { }
void drawellipticarc (float xc, float yc, float radx, float rady,
float startang, float angextent) { }
void fillarc (float xcen, float ycen, float rad, float startang,
float angextent) { }
void fillellipticarc (float xc, float yc, float radx, float rady,
float startang, float angextent) { }
void drawtext (float xc, float yc, const char *text, float boundx) { }
void clearscreen (void) { }
void create_button (const char *prev_button_text , const char *button_text,
void (*button_func) (void (*drawscreen) (void))) { }
void destroy_button (const char *button_text) { }
int init_postscript (const char *fname) {
return (1);
}
void close_postscript (void) { }
void report_structure(t_report*) { }
void set_mouse_move_input (bool) { }
void set_keypress_input (bool) { }
void set_draw_mode (enum e_draw_mode draw_mode) { }
void enable_or_disable_button(int ibutton, bool enabled) { }
void change_button_text(const char *button_text, const char *new_button_text) { }
#ifdef WIN32
void win32_drawcurve(t_point *points, int npoints) { }
void win32_fillcurve(t_point *points, int npoints) { }
#endif // WIN32 (subset of commands)
#endif // NO_GRAPHICS