/* * Program: Family Finance Demonstrator * Programmer: Robert John Morton UK-YE572246C * Started: Tue 27 Jul 2021 08:40:00 -03 * Finished: Tue 08 Aug 2021 16:44;00 -03 [13 days] Tab 0 shows the account balances across the selected year 1991 to 2001 incl- usive. Demonstrates the impossible struggle to save enough money for annual payments while striving to keep the total level of savings below the £3000 Savings Limit Penalty Threshold. If the combined level in both husband's and wife's current accounts is seen to stray above this threshold, the level of one's Welfare payments is reduced until it drops below this threshold again. Tabs 1 to 7 display data covering the 34 years from 1967 to 2000 inclusive. Each data file contains 35 4-byte integers [140 bytes in all]. The first is the starting plot for the first year and the remaining 34 are the end plots for each year. A particular year's end plot is the next year's start plot. Each decade contains years 1 to 10 inclusive. Thus, the 1990s include their 10th year, namely the year 2000. From the directory in which money.c resides, to compile: gcc famfin.c -L/usr/X11R6/lib -o famfin -lX11 to run: ./famfin A comprehensive discourse about this program and its use is at: [website]/book/chap05.html More precise live links are displayed at the bottom of each Tab in the program. */ #include // contains the X11 grapical drawing functions #include // input output to/from the console and keyboard #include // general functions for the 'C' language #include // for initialising strings as character arrays #include // contains the function usleep() #define WHITE 0xFFFFFF // colour white for 'selected menu item' lettering #define BLACK 0x000000 // colour black for clearing graph areas #define GREY 0xAAAAAA // colour grey for 'unselected menu item' lettering #define DARK 0x444444 // colour for menu background #define YELLOW 0xFFFF00 // colour yellow for graphical traces #define GREEN 0x60FF60 // colour green for graphical traces #define RED 0xFF2222 // savings limit threshold #define BLUE 0x7070FF // for husband's current account #define PINK 0xFF66AA // for wife's current account #define LM 34 // left margin of application #define TM 60 // top margin of the tab's specific content #define BW 73 // button width + inter-button space [pixels] // PERTAINING TO TAB1: THE DSS SAVINGS LIMIT PENALTY THRESHOLD #define SH 67 // x-coord of left of graph area #define SV 66 // y-coord of top of graph area #define SX 364 // horizontal extent of graph area #define SY 200 // vertical extent of graph area #define Sh 465 // left margin of right-hand side menu LM + 430 #define Sv 71 // baseline of side menu's title TM + 11 // PERTAINING TO TABS 1 TO 7 #define TH 79 // x-coord of top left corner of graph area #define TV 66 // y-coord of top left corner of graph area #define TX 200 // horizontal extent of graph area #define TY 200 // vertical extent of graph area #define Tx 114 // x-coord of start plot #define Ty 266 // y-coord of the graph's base line #define Th 335 // x-coord of right-hand side selector menu #define Tv 71 // y-coord of base line of side-menu's title #define Ts 72 // x-coord of vertical scale line #define Tm 66 // x-coord of start of vertical scale marks #define Ta 35 // x-coord of vertical scale's annotations #define Tb 71 // y-coord of base line of vertical scale's annotations #define Tc 273 // y-coord of horizontal scale line #define Td 279 // y-coord of bottom of horizontal scale's marks #define Te 290 // y-coord of base line of horizontal scale's annotations #define Tf 92 // x-coord of start of horizontal scale's annotations #define Tg 321 // y-coord of baseline of lower [inflation mode] menu Display *XD; // pointer to an X11 display device Window XW; // reference for the window being created GC XG; // X11 graphics context FILE *F; // pointer for the data files // NOT IMPLEMENTED IN THIS PROGRAM YET char *WS = "https://robmorton.website/"; int ws = 26; // number of characters in website string WS char *BR = "xdg-open "; // default browser int br = 9; // number of characters in website string BR int // GENERAL INTEGERS mX, // x-coordinate of mouse click mY, // y-coordinate of mouse click TB = 0, // tab number [re buttons across the top of the window area] PT, // contains cyclic sleep time in microseconds RS = 1, // 0:RUN, 1:STOP, 2:CLEAR // vertical coordinates of graph's Baseline [y = 0] for each tab A[] = {Ty-40,Ty,Ty,Ty,Ty,Ty-40,Ty-120,Ty-40}, // TAB INTEGER VARIABLES TF = 0, // inflation menu selection Tl = 0, // not a leap year [Tl=1 means it's a leap year] DF = 0, // default disaplay flag MI = 0, // side-menu's 'currently selected item' number DM[] = {31,28,31,30,31,30,31,31,30,31,30,31}, // DAYS IN EACH MONTH TL[] = {0,1,0,0,0,1,0,0,0,1,0}, // leap years P[366][2], // for holding one year's current account balances in Tab 0 Q[35]; // for holding the 34 years of plots in Tabs 1 to 7 double IF[] = { // Inflation multipliers relative to year 2000 11.06377835626627, // 1966 = 0 10.686242816091953, // 1967 = 1 10.418665732796358, // 1968 = 2 9.844639311714097, // 1969 = 3 9.40132722389003, // 1970 = 4 8.712988724557036, // 1971 = 5 8.014682112068966, // 1972 = 6 7.442276422764228, // 1973 = 7 6.721757794848622, // 1974 = 8 5.651690729483283, // 1975 = 9 4.521696177521088, // 1976 = 10 3.9271995247838425, // 1977 = 11 3.4998529498264808, // 1978 = 12 3.229712858926342, // 1979 = 13 2.7560794849228776, // 1980 = 14 2.3934432823813356, // 1981 = 15 2.136634587762137, // 1982 = 16 2.0278440460772953, // 1983 = 17 1.9251625845277769, // 1984 = 18 1.8404837761761885, // 1985 = 19 1.7426998213396596, // 1986 = 20 1.6796804426377596, // 1987 = 21 1.619515514425694, // 1988 = 22 1.5167605597899514, // 1989 = 23 1.4082410300104138, // 1990 = 24 1.2879004329004329, // 1991 = 25 0 for balances graph 1.2328491805316701, // 1992 = 26 1 1.2018461662761575, // 1993 = 27 2 1.1789847031782517, // 1994 = 28 3 1.1458806763471094, // 1995 = 29 4 1.1099897397630818, // 1996 = 30 5 1.0835503432702638, // 1997 = 31 6 1.0456198928037959, // 1998 = 32 7 1.0176329741747905, // 1999 = 33 8 1.0000000000000000 // 2000 = 34 9 }; /* VERTICAL MONEY SCALES BY TAB NUMBER [V0 = TAB0] This array needs to be declared as common variables because it must be byte-picked in main() to change the currency sign. */ char V0[][4] = {"$5k","$4k","$3k","$2k","$1k","$0k"}, V1[][6] = {"+$40k","+$30k","+$20k","+$10k"," $00k","-$10k"}, V2[][6] = {" $10k"," $ 8k"," $ 6k"," $ 4k"," $ 2k"," $ 0k"}, V3[][6] = {" $ 5k"," $ 4k"," $ 3k"," $ 2k"," $ 1k"," $ 0k"}, V4[][6] = {"$2.5k","$2.0k","$1.5k","$1.0k","$0.5k","$0.0k"}, V5[][6] = {" $25k"," $20k"," $15k"," $10k"," $ 5k"," $ 0k"}, V6[][6] = {" +$40"," +$30"," +$20"," +$10"," $ 0"," -$10"}, V7[][6] = {"+$20k","+$10k"," $00k","-$10k","-$20k","-$30k"}, (*V[])[6] = { // index pointer access to the above for Tabs 1 to 7 V1, // for Tab 1 V2, // for Tab 2 V3, // for Tab 3 V4, // for Tab 4 V5, // for Tab 5 V1, // for Tab 6's money scale V7, // for Tab 7's items 1 to 7 on the right-hand side menu V6 // for Tab 7's 'per-person-per-day' [8th item on menu] }, /* INFLATION MENU NOTE: this array needs to be declared as common variables because it needs to be byte-picked in main() to change the currency sign. */ IM[][47] = { "Show the graph as original monetary values.", "Show the graph inflation-corrected to $Y2k.", // char 42 is pound strlg "Show both real & inflation-corrected plots." }, /* A 70-character array for storing the hyperlink access command line for each tab. The appropriate remainder of the command line [given in *H[]] for each hyperlink is added to the static text from character 39 onwards when the link is clicked in a particular tab. */ HL[70] = "xdg-open https://robmorton.website/book/", // 40 chars /* This is the static part of the hyperlink AS DISPLAYED at the botton left of each tab. The extra text for each respective tag, as given in *H[] below, is added, as appropriate, from character 30 onwards. */ *HD = "https://robmorton.website/book/", // 31 chars // The different and particular extensions the hyperlinks for each Tab. *H[] = { "chap06/savlim.html#applet &", "chap05.html#income_total &", "chap05.html#taxation &", "chap05.html#fixedcosts &", "chap05.html#utils &", "chap05.html#carcost &", "chap05.html#summary &", "chap05.html#analysis &" }, K[][9] = {"Maximum $","Minimum $","Average $","Swing $"}; /* DRAW THE HELP HYPERLINK AT THE BOTTOM LEFT OF EACH TAB Called from one place each in Tabs() and Btab(). */ void Hypr() { const int h[] = {25,24,20,22,17,19,19,20}; XSetForeground(XD,XG,YELLOW); // colour for hyperlink XDrawString(XD,XW,XG,LM,TM+387,HD,30); XDrawString(XD,XW,XG,LM+180,TM+387,*(H+TB),*(h+TB)); } /* GET A 32-BIT INTEGER FROM A 4-BYTE TRAIN IN A FILE Called from two places each in Plot() and Blot(). */ int getInt() { // 'F' is the one and only global file handle return fgetc(F) << 24 // shift first byte to left of 32-bit int | fgetc(F) << 16 // shift second byte to 2nd slot from the left | fgetc(F) << 8 // shift 3rd byte to 2nd slot from the right | fgetc(F); // leave 4th byte where it is at the far right } /* WIPE THE GRAPH AREA AND RE-DISPLAY THE GRATICULE LINES Called from one place each in Plots() and Tabs(). */ void Grat() { XSetForeground(XD,XG,BLACK); // background colour XFillRectangle(XD,XW,XG,TH,TV,TX,TY); // wipe the graph area int i, // looping variable a = TV, // y-coord of top of graph area b = TH+TX; // x-coord of end of graph area XSetForeground(XD,XG,DARK); // graticule colour for(i = 0; i < 6; i++) { // for each monetary graduation XDrawLine(XD,XW,XG,TH,a,b,a); // [next] horizontal graticule line a += 40; } // Ty = y-coord of bottom of graph area a = TH; // y-coord of start of horiz graticule for(i = 0; i < 5; i++) { // for each decade shown XDrawLine(XD,XW,XG,a,Ty,a,TV); // vertical graticule line a += 50; // accumulated number of days } } /* TEMPORARY Mwh SCALES USED IN TAB4 [THE UTILITIES TAB] Called from only one place in Plot(). */ void MwhScales() { const char S[2][6][5] = { {"5Mwh ","4Mwh ","3Mwh ","2Mwh ","1Mwh ","0Mwh "}, // for electricity {"50Mwh","40Mwh","30Mwh","20Mwh","10Mwh","00Mwh"} // for gas }; int a = TV, // vertical start-point for marks on vertical scale b = TH+TX+6; // x-coord of right-hand vertical scale line XSetForeground(XD,XG,BLACK); // clear the scale area to XFillRectangle(XD,XW,XG,b,a-5,40,210); // the right of the graph if(MI > 1) return; // don't draw scale for other than electricity or gas XSetForeground(XD,XG,RED); // annotation colour for(int i = 0; i < 6; i++) { // for each Mwh graduation XDrawLine(XD,XW,XG,b,a,b+5,a); // [next] horizontal annotation mark XDrawString(XD,XW,XG,b+7,a+5,S[MI][i],5); // draw annotation lettering a += 40; // accumulated number of £s (scaled) } XDrawLine(XD,XW,XG,b,TV,b,Ty); // draw the vertical axis } /* DRAWS THE TRACE FOR GRAPHS IN TABS 1 TO 7 Called only from 3 places in Plot() */ void Trace(int C) { const int D[]={25000,5000,2500,1250,12500,25000,25000,25}, // pence divisors E[]={2500,25000}; // energy [kwh] divisors int z=TB-1, // index number 0 to 7 for this current tab q = 0; // index offset for the 'per person per day' graph if(z == 6 // if we're doing the analysis tab && MI == 7) // and item 7 'per person per day' is selected q = 1; // shift the index one place up int a=A[z+q], // vertical coordinate of graph's Baseline [y = 0] b=Tx, // horizontal coordinate of first plot [x = 0] c; double y = Q[0]; // start point for the graph [left side of first plot] if(z == 3 && MI < 2) // if utilities tab and electricity or gas selected c=y/E[MI]; // scale the plot from kwh to pixels else // otherwise c=y/D[z+q]; // scale the plot from pence to pixels XSetForeground(XD,XG,C); // plots colour for(int i = 1; i < 34; i++) { // for each year double y=Q[i]; // get pence [or kwh] for this year int d; if(z == 3 && C == RED) { // utilities tab and electricity or gas selected d=y/E[MI]; // scale the plot from pence to pixels } else { if(C == YELLOW) // if it is an inflation-corrected display y *= IF[i]; // multiply it by the inflation factor d=y/D[z+q]; // scale the plot from pence to pixels } if(d > 200) d=200; // chop at top of graph [safety measure] int e=a-d; // horizontal end-point of this plot if(C != RED // if it isn't an energy graph || c != 0) // or the vertical plot is non-zero, XDrawLine(XD,XW,XG,b,a-c,b,e); // then display it if(C != RED // if it isn't an energy graph || d != 0) // or the horizontal plot is non-zero, XDrawLine(XD,XW,XG,b,e,b+5,e); // display it b += 5; // move onwards to the next plot c = d; // this time's end-point is next time's start point } } /* OPEN THE SELECTED INCOME SOURCE FILE, LOAD THE DATA INTO Q[] AND DISPLAY THE GRAPHICAL DATA ON THE GRAPH. The data file gives values in pence. This must be scaled to express it in pixels. So divide the monetary amounts by the appropriate scaling factor for each graph to get the heights of the plots. Years 1967 to 2000: 34 years. */ void Plot() { static char *S1[] = {"inc_me.dat","incwif.dat","incwel.dat", "inc_cb.dat","incdla.dat","inctot.dat"}, *S2[] = {"taxinc.dat","taxni4.dat","taxni1.dat", "tax_cc.dat","taxwat.dat","taxtot.dat"}, *S3[] = {"fixmor.dat","fixend.dat","fixhse.dat", "fixcon.dat","fixtot.dat"}, *S4[] = {"utelec.dat","utgas.dat","utmai.dat","utpho.dat", "uttv.dat","uttot.dat","elekwh.dat","gaskwh.dat"}, *S5[] = {"carpch.dat","cartax.dat","carins.dat","carpet.dat", "carmai.dat","carpkg.dat","cartot.dat"}, *S6[] = {"inctot.dat","sp_tax.dat","sp_fix.dat", "sp_uti.dat","sp_car.dat"}, *S7[] = {"surp3.dat","surp1.dat","surp4.dat","surp2.dat", "budsur.dat","buddeg.dat","pppy.dat","pppd.dat"}, **S[] = {S1,S2,S3,S4,S5,S6,S7}; int z=TB-1; // this tab's index number 0 to 7 F = fopen(S[z][MI],"rb"); // Open the data file for the selected // item in the selected tab. for(int i = 0; i < 35; i++) { // get the the 35 plots for the 34 years if(feof(F)) break; // safety measure Q[i] = getInt(); // put the 'i'th plot in the plots array } fclose(F); // close the data input file Grat(); // wipe the graphical area if(TF == 0 || TF == 2) // if 'uninflated' or 'both' selected Trace(GREEN); // display the uniflated trace if(TF == 1 || TF == 2) // if 'inflated' or 'both' selected Trace(YELLOW); // display the inflated trace // FOR THE UTILITIES TAB'S ENERGY TRACES if(z == 3) { // if doing the Utilities Tab if(MI < 2) { // if electricity or gas selected F = fopen(S[z][MI+6],"rb"); // open the respective 'kwh' file for(int i = 0; i < 35; i++) { // put the 35 plots in the Q[] array if(feof(F)) break; // safety measure Q[i] = getInt(); // annual electricity/gas energy } // for year 'i' in kwh fclose(F); // close the energy plots file Trace(RED); // display the energy consumption graph } MwhScales(); // show or wipe the energy scale as according to MI [z] } } /* FACILITATES THE SELECTION OF THE ORIGINAL AMOUNTS OF MONEY, THE INFLATED AMOUNTS OR BOTH ON THE DISPLAYED GRAPH. Called from one place each in Tabs() and Click(). */ void Infl() { static int // to make selection same colour as trace C[3] = {GREEN,YELLOW,WHITE}; int X, I = 3, // number of items in the inflation menu x = LM + 276, // length of 'inflation menu' item text y = Tg; // 2 pixels below the baseline 'uninflated' item TF = 1; // set default inflation menu selection if(TB == 0) { // if it's the 'Balances' tab y = TM + 344; // 2 pixels below the baseline of 'actual balances' I = 2; // number of items in 'Balances' tab's inflation menu } for(int i = 0; i < I; i++) { // for each of the 3 inflation menu items if(DF == 0) { // if not setting the default item // if mouse was clicked on the item for this pass of the loop if(mY <= y && mY >= y-18 && mX <= x) { TF = i; // set the current selection X = C[i]; // set selected highlighting colour } else // else click was outside this item's rectangle X = GREY; // set colour to grey for unselected item } else { // else we are setting the default menu item if(i == 1) { // so if this is the second pass [i=1] TF = i; // note index number of the selected item X = C[i]; // set appropriate highlighting colour } else // else this pass is not the default item X = GREY; // so display it in grey } XSetForeground(XD,XG,X); // set appropriate colour for the menu item XDrawString(XD,XW,XG,LM,y-2,IM[i],43); y += 18; // drop down to next menu line } } /* DISPLAY THE SELECTOR MENU ON THE RIGHT-HAND SIDE OF THE GRAPH Called from one place each in Tabs() and Click(). */ void Menu() { const char *S0[] = {"1991","1992","1993","1994","1995","1996", "1997","1998","1999","2000","2001"}, *S1[] = {"Husband's income","Wife's Income","Welfare [Income Support]", "Child Belefit","Disability Living Allowance","Total Income"}, *S2[] = {"Income Tax","NI Class 4","NI Class 1", "Council Tax","Water Rates","Total Taxes"}, *S3[] = {"Mortgage Payments","Endowment Policy","House Insurance", "Contents Insurance","Total Fixed Costs"}, *S4[] = {"Electricity","Gas","Boiler Maintenance", "Telephone","Television Licence","Total"}, *S5[] = {"Capital Cost","Road Tax","Insurance","Fuel", "Maintenance","Parking","Total Cost"}, *S6[] = {"A = Gross Income","B = A - all Direct Taxes", "C = B - Fixed Costs","D = C - Utilities Costs", "E = D - Car Costs"}, *S7[] = {"SI on Survival Budget","SI on SB if we had no car", "SI on Degradation Budget","SI on DB if we had no car", "Survival Budget","Degradation Budget", "Per Person Per Year","Per Person Per Day"}, **S[] = {S0,S1,S2,S3,S4,S5,S6,S7}; const int s0[] = {4,4,4,4,4,4,4,4,4,4,4}, // lengths of the above strings s1[] = {16,13,24,13,27,12}, s2[] = {10,10,10,11,11,11}, s3[] = {17,16,15,18,17}, s4[] = {11,3,18,9,18,5}, s5[] = {12,8,9,4,11,7,10}, s6[] = {16,24,19,23,17}, s7[] = {21,25,24,25,15,18,19,18}, *s[] = {s0,s1,s2,s3,s4,s5,s6,s7}, // pointers to the above strings N[] = {11,6,6,5,6,7,5,8}; // number of items in each menu int X, // menu item colour x=Th, // left edge of menu items for Tabs 1 to 8 y=Tv+22; // 2 pixels lower than baseline of first item if(TB == 0) // if we're doing the 'Balances' tab [Tab 0] x=Sh; // left edge of menu items for Tab 0 int I = N[TB]; // number of items in this menu MI = 0; // default selection for the right-hand menu for(int i = 0; i < I; i++) { // for each item in the menu if(DF == 0) { // if not setting the default item if(mY <= y && mY > y-18) { // if click was on the 'i'th item number MI = i; // note the selected item number X = GREEN; // colour for selected menu item } else // else click was outside this item's rectangle X = GREY; // set colour to grey for unselected item } else { // else we are setting the default menu item if(i == 0) { // so if this is the second pass [i=1] MI = i; // note the selected item number X = GREEN; // colour for selected menu item } else // else this pass is not the default item X = GREY; // so display it in grey } XSetForeground(XD,XG,X); // set appropriate colour for menu item XDrawString(XD,XW,XG,x,y-2,S[TB][i],s[TB][i]); y += 18; // drop down to next year number } } /* HORIZONTAL TIME SCALE [DECADES] FOR TABS 1 TO 7 Called from only one place in Tabs(). */ void Horz() { static char *S[] = {"1960s","1970s","1980s","1990s"}; int a = TH; // start POINT of horizontal axis for(int i = 0; i < 4; i++) { // for each decade shown XDrawString(XD,XW,XG,a+12,Te,*(S+i),5); XDrawLine(XD,XW,XG,a,Tc,a,Td); // decade boundary mark a += 50; // accumulated number of pixels } XDrawLine(XD,XW,XG,a,Tc,a,Td); // final decade boundary mark XDrawLine(XD,XW,XG,TH,Tc,TH+TX,Tc); // horizontal axis } /* DRAW THE VERTICAL MONEY SCALE [TABS 1 TO 7] Called from only one place in Tabs() below. */ void Vert() { XSetForeground(XD,XG,BLACK); // clear the scale area XFillRectangle(XD,XW,XG,LM,TV-5,42,TY+10); int z=TB-1, // index number 0 to 7 for this current tab q = 0; // index offset for the 'per person per day' graph if(z == 6 // if we're doing the analysis tab && MI == 7) // and item 7 'per person per day' is selected q = 1; // shift the index one place up int a = TV, // vertical start-point for marks on the vertical scale b = TB-1+q; // tab scales index number XSetForeground(XD,XG,GREY); // annotation colour for(int i = 0; i < 6; i++) { // for each monetary graduation XDrawLine(XD,XW,XG,Tm,a,Ts,a); // [next] horizontal annotation mark XDrawString(XD,XW,XG,LM,a+5,V[b][i],5); // draw annotation lettering a += 40; // accumulated number of vertical pixels } XDrawLine(XD,XW,XG,Ts,TV,Ts,Ty); // draw the vertical axis } /* DISPLAY THE STAIC CONTENT OF THE SELECTED TAB Called from only one place in showTab(). */ void Tabs() { const char // headings for the right-hand side menus *S[] = {"INCOME SOURCES","TAXATION","HOUSE COSTS","UTILITIES", "CAR COSTS","SPENDABLE INCOME","THE BOTTOM LINE"}; const int // number of chars in each menu heading s[] = {14,8,11,9,9,16,15}; int i=TB-1; // menu heading index number for selected tab XSetForeground(XD,XG,GREY); // colour for menu lettering XDrawString(XD,XW,XG,Th,Tv,S[i],s[i]); // draw right-hand side menu Vert(); // show the selected tab's vertical money scale on the left Horz(); // show the selected tab's horizontal years scale Grat(); // show the selected tab's graph graticule DF = 1; // set the 'set default menu item' flag Menu(); // show default setting of top right-hand menu Infl(); // show default setting of the inflation menu Plot(); // display the graph for the selected year DF = 0; // unset the 'set default menu item' flag Hypr(); // show the appropriate HELP hyperlink for the selected tab MI = 0; // select menu item 0 by default } /* CONVERT AN INTEGER TO AN 8-DIGIT NUMERIC STRING WITH LEADING ZEROS AND 2 DECIMAL PLACES. Called from 4 places in Blot(). */ char *showInt(int x) { static char S[9]; // to hold the string version of the number for(int i = 0; i < 8; i++) // for each of the 8 digit positions S[i] = '0'; // fill the whole field with zeros S[5] = '.'; // put in the decimal point S[8] = (char)0; // put in the terminating null character for(int i = 7; i > -1; i--) // for each of the 8 digits if(i != 5) { // provided we are not on the decimal point int y = x % 10; // find the current least significant digit S[i] = y + 48; // set it as a character in its array element x /= 10; // divide the presented integer by 10 } return S; // return the address of the char array containing the number } /* WIPES THE GRAPH AREA AND RE-DRAWS THE GRATICULE LINES FOR 'BALANCES' TAB Called from only one place each in Blot() and Btab(). */ void Brat() { XSetForeground(XD,XG,BLACK); // set background colour XFillRectangle(XD,XW,XG,SH,SV,SX+Tl+3,SY+6); // clear graticule area int i, // looping variable a = SV, // y-coord of top of graph area b = SH+SX+Tl, // x-coord of end of graph area c = SV+SY; // y-coord of bottom of graph area XSetForeground(XD,XG,DARK); // graticule colour for(i = 0; i < 6; i++) { // for each £1000 graduation XDrawLine(XD,XW,XG,SH,a,b,a); // [next] horizontal graticule line a += 40; // advance to the next line } a = SH; // y-coord of start of horiz graticule for(i = 0; i < 12; i++) { // for each month shown int g = DM[i]; // get the number of days in this month if(i == 2) g += Tl; // if February, add possible leap day XDrawLine(XD,XW,XG,a,c,a,SV); // draw the vertical graticule line a += g; // accumulated number of days } XDrawLine(XD,XW,XG,a,c,a,SV); // draw the final vertical graticule line } /* DISPLAY THE ACCOUNT BALANCES GRAPH Called from one place in Btab() and two places in Click() */ void Blot() { static char S[12][12] = {"sav1991.dat","sav1992.dat","sav1993.dat","sav1994.dat", "sav1995.dat","sav1996.dat","sav1997.dat","sav1998.dat", "sav1999.dat","sav2000.dat","sav2001.dat"}; /* OPEN THE YEAR FILE AND LOAD THE ACCOUNT DATA INTO P[][] The data file gives values in pence. The graph scale is 500000 pence = 200 pixels so that's 2500 pence per pixel. So divide the monetary amounts by 2500 to get the heights of the plots. */ F = fopen(S[MI],"rb"); int i, // general loop variable a, // husband's account balance for a given day b, // wife's balance for a given day c, // sum of the two current accounts for a given day mx=0, // maximum combined balance to date mn=5000000, // minimum combined balance to date av, // average combined balance to date sw; // average maximum to minimum swing over the year to date double q=0; for(i = 0; i < 365; i++) { // for each day of the year if(feof(F)) break; // safety measure a = getInt(); // husband's balance for day 'i' in pence b = getInt(); // wife's balance for day 'i' in pence if(TF == 1) { // if inflation mode is selected a *= IF[MI+25]; // multiply both account balances b *= IF[MI+25]; // by the inflation factor to get Y2k values } c = a + b; // total for both current accounts if(c > mx) mx = c; // capture the maximum value so far [in pence] if(c < mn) mn = c; // capture the minimum value so far [in pence] q += c; // accumulate the total for computing the average P[i][0] = a / 2500; // husband's balance for day 'i' in pixels P[i][1] = b / 2500; // wife's balance for day 'i' in pixels } fclose(F); // close the data input file av = (int)(q / 365); // average balance throughout the whole year sw = mx - mn; // swing over the whole year Brat(); // wipe the graph area and re-draw the graticule // SHOW THE MAXIMUM, MINIMUM & AVERAGE BALANCES AND THE MAXIMUM SWING XSetForeground(XD,XG,BLACK); // background colour XFillRectangle(XD,XW,XG,91,309,70,70); // wipe print area XSetForeground(XD,XG,WHITE); // foreground colour XDrawString(XD,XW,XG,92,321,showInt(mx),8); // maximum XDrawString(XD,XW,XG,92,339,showInt(mn),8); // minimum XDrawString(XD,XW,XG,92,357,showInt(av),8); // average XDrawString(XD,XW,XG,92,375,showInt(sw),8); // swing // DISPLAY THE SAVINGS LIMIT PENALTY THRESHOLD IN RED double k = 1; // without inflation if(TF == 1) // if 'inflation corrected' is selected k = IF[MI+25]; // get inflation multiplier for current year b = SV+SY-120*k; // set y-coord savings limit line XSetForeground(XD,XG,RED); XDrawLine(XD,XW,XG,SH,b,SH+SX,b); // DISPLAY THE GRAPHICAL DATA a = SH; // horizontal coordinate of first plot [x = 0] b = SV+SY; // vertical coordinate of graph's baseline [y = 0] c = P[0][0]; // start point of the line to be drawn int d, // end-point of line to be drawn I = 365; // basic number of days in a year // DISPLAY THE GRAPH FOR THE HUSBAND'S CURRENT ACCOUNT IN BLUE XSetForeground(XD,XG,BLUE); // plots colour for(i = 1; i < I; i++) { // for each day of the year d = P[i][0]; // get the balance for current day of the year if(d > 200) d = 200; // chop at top of graph [safety measure] XDrawLine(XD,XW,XG,a++,b-c,a,b-d); // draw the plot line c = d; // this time's end-point is next time's start point } // DISPLAY THE GRAPH FOR THE WIFE'S CURRENT ACCOUNT IN PINK a = SH; // horizontal coordinate of THE first plot [x = 0] c = P[0][1]; // balance for the first day of the year XSetForeground(XD,XG,PINK); // plots colour for(i = 1; i < I; i++) { // for each day of the year d = P[i][1]; // balance for current day of the year if(d > 200) d = 200; // chop at top of graph [safety measure] XDrawLine(XD,XW,XG,a++,b-c,a,b-d); // draw the plot line c = d; // this time's end-point is next time's start point } // DISPLAY THE GRAPH FOR THE SUM OF THE TWO ACCOUNTS IN GREEN a = SH; // horizontal coordinate of the first plot [x = 0] c = P[0][0] // sum of the husband's account + P[0][1]; // plus the wife's account for the first day XSetForeground(XD,XG,GREEN); // plots colour for(i = 1; i < I; i++) { // for each day of the year d = P[i][0] + P[i][1]; // balance for the sum of both accounts if(d > 200) d = 200; // chop at top of graph [safety measure] XDrawLine(XD,XW,XG,a++,b-c,a,b-d); // draw the plot line c = d; // this time's end-point is next time's start point } } /* DRAW ALL THE STATIC CONTENT OF THE BALANCES TAB Called only from one place in showTab(). */ void Btab() { const char S[][4] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; // DRAW THE VERTICAL MONEY SCALE int i, // looping variable a = 6, // vertical start-point for marks on vertical scale b = LM + 25, // end-point of vertical scale's marks c = LM + 19, // horizontal start-point of vertical scale's marks d = TM + 5, // base height bias of money annotations e = SH+SX+Tl, // end-point of horizontal axis g; XSetForeground(XD,XG,GREY); // annotation colour for(i = 0; i < 6; i++) { // for each £1000 graduation g = TM + a; XDrawString(XD,XW,XG,LM,d+a,V0[i],3); // draw annotation lettering and XDrawLine(XD,XW,XG,c,g,b,g); // horizontal annotation mark a += 40; // accumulated number of vertical pixels } g += 7; // horizontal axis is 7 pixels below the bottom graticule XDrawLine(XD,XW,XG,SH,g,e,g); // draw horizontal axis // DRAW THE HORIZONTAL MONTHS SCALE a = SH; // starting point of horizontal graticule b = TM + 213; // upper [starting] point of month boundary marks c = b + 5; // lower [end] point of month boundary marks d = b + 20; // base height of month names for(i = 0; i < 12; i++) { // for each month shown g = DM[i]; // get number of days in this month if(i == 2) g += Tl; // in case it's a leap year XDrawString(XD,XW,XG,a+7,d,S[i],3); XDrawLine(XD,XW,XG,a,b,a,c); // month boundary mark a += g; // accumulated number of days } XDrawLine(XD,XW,XG,a,b,a,c); // final month boundary mark a = SH - 7; // horizontal coord of vertical axis XDrawLine(XD,XW,XG,a,SV+SY,a,SV); // vertical [money] axis Brat(); // draw the 'Balances' graph graticule // DISPLAY THE YEARS MENU 1991 TO 2001 XSetForeground(XD,XG,GREY); // colour for panel lettering XDrawString(XD,XW,XG,Sh,Sv,"YEAR",4); // draw the menu title DF = 1; // set the 'default selection' flag Infl(); // show default setting of inflation menu Menu(); // show default setting of year menu Blot(); // display the graph for the selected year DF = 0; // unset the 'default selection' flag // DRAW THE STATISTICS TITLES b = TM + 261; XSetForeground(XD,XG,GREY); // colour for panel lettering XDrawString(XD,XW,XG,LM,b, K[0],9); XDrawString(XD,XW,XG,LM,b+18,K[1],9); XDrawString(XD,XW,XG,LM,b+36,K[2],9); XDrawString(XD,XW,XG,LM,b+54,K[3],9); // DRAW TRACE NOTES a = LM + 150; b = TM + 261; XDrawString(XD,XW,XG,a,b, "TRACE KEY",9); XDrawString(XD,XW,XG,a,b+18,"BLUE = Husband's account",24); XDrawString(XD,XW,XG,a,b+36,"PINK = Wife's account",21); XDrawString(XD,XW,XG,a,b+54,"GREEN = Sum of the two",22); Hypr(); // show hyperlink at bottom of Tab 0 // SAVINGS LIMIT PENALTY THRESHOLD NOTE XSetForeground(XD,XG,RED); a = 389; b = 318; c = 17; XDrawString(XD,XW,XG,a,b, "The horizontal red line in the graph",36); XDrawString(XD,XW,XG,a,b+=c,"shows the level of the Savings Limit",36); XDrawString(XD,XW,XG,a,b+=c,"Penalty Threshold. If the balance in",36); XDrawString(XD,XW,XG,a,b+=c,"your combined current accounts veers",36); XDrawString(XD,XW,XG,a,b+=c,"above this threshold, the amount of",35); XDrawString(XD,XW,XG,a,b+=c,"Welfare Benefit you receive is prop-",36); XDrawString(XD,XW,XG,a,b+=c,"ortionally reduced below the norm.",34); } /* PROCESS A MOUSE CLICK MADE WITHIN THE SELECTED TAB Called from only one place in MEH(). */ void Click() { const int // number of menu items for this tab x 18 pixels + 3 Y[] = {198,112,112,112,112,130,98,148}; if(mY > Tv+3 && mY < Tv+Y[TB] // If the click was within && mX >= Th && mX <= Th+164) { // the top right menu: if(TB == 0) { // if we're in Tab 0 Menu(); // display the YEAR menu with selected item highlighted Blot(); // display the graph for the selected year } else { // otherwise, for Tabs 1 to 8: Menu(); // display the menu with the selected item highlighted Plot(); // display the graph for the selected item if(TB == 7) // Need to re-display the left-hand money scale for the Vert(); // 'per-person-per-day' graph and also revert back again. } } else if(mX > 33 && mX <291) { // if within width of inflation menu if(TB == 0) { // if in Tab 0 'Balances' if(mY > 391 && mY < 421) // if within vertical limits of inflation Infl(); // menu, highlight the selected item Blot(); // and plot the appropriate graph } else { // otherwise we're in one of menus 1 to 8 if(mY > 309 && mY < 356) // so if within vertical limits of their Infl(); // inflation menus, highlight the selected Plot(); // inflation mode & plot appropriate graph } } } // COMMON FUNCTIONS------------------------------------------------------------ /* DISPLAY ALL THE TITLES, BUTTONS, MENUS AND DEFAULT IN-FILL FOR CURRENT TAB which is specified by the numeric value of the global variable 'TB': Called from only 1 place each in main() and MEH(). */ void showTab() { XSetForeground(XD,XG,BLACK); // set background colour XFillRectangle(XD,XW,XG,0,0,640,480); // and clear the Tab area const char // TAB BUTTON LABELS *BA[8] = { "Balances","Income","Taxation","House", "Utilities","Car","Summary","Analysis" }; const int NC[8] = {8, 6,8, 5,9, 3,7,8}, // number of chars in each annotation NB[8] = {6,12,6,15,4,22,9,7}; // x-bias for each annotation XSetForeground(XD,XG,DARK); // shade button background int h = LM; // horizontal left edge of first button for(int i = 0; i < 8; i++) { // draw each button XFillRectangle(XD,XW,XG,h,17,BW-12,21); // shade in the button area h += BW; // advance to the next button } h = LM; // horizontal left edge of first button for(int i = 0; i < 8; i++) { // annotate each button if(TB == i) // if annotating the selected button XSetForeground(XD,XG,WHITE); // annotated it in WHITE else // if it's any of the other buttons XSetForeground(XD,XG,GREY); // annotate it in GREY XDrawString(XD,XW,XG,h + *(NB+i),32,*(BA+i),*(NC+i)); h += BW; // advance to the next button } if(TB == 0) // if Tab 0 is selected Btab(); // do the special static infill for it else // otherwise one of Tabs 1 to 8 is selected Tabs(); // so do the static infill for the appropriate Tab } /* MOUSE EVENT HANDLER. Handles clicks of the left mouse button [Button 1]. The 'x' and 'y' coordinates of the click point within the application window are already in the global variables 'mX' and 'mY'. Called only from one place in main()'s event loop. */ void MEH() { int i; // number 0 to 7 of clicked button [8 if no button clicked] if(mY > 16 && mY < 38) { // if click on one of the Tab buttons for(i = 0; i < 8; i++) { // for each button int y = i * BW; // button offset in pixels if(mX > LM + y && mX < LM + 61 + y) // if click was within the button break; // break out of the for() loop } if(i < 8) { // provided 'i' is between '0' and '7' TB = i; // set Tab Number to index number of 'break' showTab(); // display the Tab's fixed content } } // else, if the mouse was clicked within the hyperlink area: else if(mX > 34 && mY > 437 && mX < 450 && mY < 448) { sprintf(HL+39,"%s",H[TB]); // lay name of selected tab's help file into system(HL); // the hyperlink and open it in web browser } else // else the click was somewhere else in the current tab, if(TB == 0) // so if we're in the 'Balances' tab Click(); // process the click for Tab 0 else // else the click was in one of tabs 1 to 8 Click(); // so check to see if a menu item was clicked there } /* MAKE PRE-RUN DATA ADJUSTMENTS FOR THIS PARTICULAR PROGRAM Called only from one place in main(). */ void prep() { for(int i = 0; i < 6; i++) { // put British pound sterling sign V0[i][0] = 0xA3; // in the appropriate annotation arrays V1[i][1] = 0xA3; V2[i][1] = 0xA3; // 0xA3 is the lower byte of the UTF-8 V3[i][1] = 0xA3; // two-byte character representing the V4[i][0] = 0xA3; // pound sterling currency sign. V5[i][1] = 0xA3; // The X character display interprets V6[i][2] = 0xA3; // this UTF-8 half-character corrrectly V7[i][1] = 0xA3; // as a British pound Sterling sign. } IM[1][38] = 0xA3; // and in the inflation menu K[0][8] = 0xA3; // change to pound Sterling sign K[1][8] = 0xA3; // in current account statistics K[2][8] = 0xA3; K[3][8] = 0xA3; } // MAIN ----------------------------------------------------------------------- int main(void) { XEvent e; // reference to an event from the created window int s; // number of the current default screen /* Check that you can actually open the X-display by getting a non-NULL reference to a display. Else send an error message to the terminal. */ XD = XOpenDisplay(NULL); if(XD == NULL) { fprintf(stderr,"Cannot open display\n"); exit(1); } s = DefaultScreen(XD); // get the number of the default screen XG = DefaultGC(XD,s); // set the graphics context to use XW = XCreateSimpleWindow( // create an application window XD, // within the default display RootWindow(XD,s), // within the root window [desktop] 10, 10, // position of top right corner 640, 480, // width and height of the window 1, // border width in pixels BlackPixel(XD,s), // colour of window's border BlackPixel(XD,s) // colour of window's background ); // set the program's window title XStoreName(XD,XW,"Family Finances by Robert John Morton YE572246C"); /* Make input possible from the window controls and mouse then display the new window on the screen. Not all these bit masks are needed so comment out those that are not needed. */ XSelectInput( XD,XW, ExposureMask | // KeyPressMask | // KeyReleaseMask | // PointerMotionMask | ButtonPressMask // | // ButtonReleaseMask | // StructureNotifyMask ); XMapWindow(XD,XW); // make window viewable on desktop when required XFlush(XD); // flush the window's output buffer /* Define a unique indentifier WM_DELETE_WINDOW (referred to as an atom) that will invoke the X11 protocol that closes windows upon the display 'XD' we have just created. [At least I think this is what it means] */ Atom WM_DELETE_WINDOW = XInternAtom(XD,"WM_DELETE_WINDOW",False); /* Make a 'delete window' event from this particular program visible within this program as a ClientMessage event. */ XSetWMProtocols(XD,XW,&WM_DELETE_WINDOW,1); prep(); // make pre-run data adjustments for this particular program /* The event-handling [or 'run'] loop [a permanent loop] broken only by the 'break' function in response to an external event. */ while(1) { if(XPending(XD) > 0) { // Provided there are events on the event queue, XNextEvent(XD,&e); // go fetch the first one from the queue. if(e.type == Expose) { // If it is the 'window ready' event, showTab(); // display current tab's content in window } else if(e.type == ButtonPress) { // if a mouse button was clicked if(e.xbutton.button == Button1) { // provided it's the left button mX = e.xbutton.x; // set the window's x-coord of the click mY = e.xbutton.y; // set the window's y-coord of the click MEH(); // pass through the mouse event handler } // then return and continue this loop } } /* Else if a box control has been clicked, break out of the permanent while() loop, close the window and exit. Should also check here for other client message types; however, as the only protocol registered above is WM_DELETE_WINDOW, it is safe for this program. */ else if (e.type == ClientMessage) break; usleep(200000); // pause the thread for a fifth of a second } // end of outer permanent while() loop XCloseDisplay(XD); // clear this window from the X11 display 'XD' return 0; // return that everything went OK } // end of main()