aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Recipe40
-rw-r--r--gtk.c37
-rwxr-xr-xmkfiles.pl2
-rw-r--r--osx.m103
-rw-r--r--puzzles.h5
-rw-r--r--version.c16
-rw-r--r--windows.c207
7 files changed, 395 insertions, 15 deletions
diff --git a/Recipe b/Recipe
index fe6c003..4c508c1 100644
--- a/Recipe
+++ b/Recipe
@@ -14,7 +14,7 @@
!makefile osx Makefile.osx
WINDOWS = windows user32.lib gdi32.lib comctl32.lib
-COMMON = midend misc malloc random
+COMMON = midend misc malloc random version
NET = net tree234
NETSLIDE = netslide tree234
@@ -84,3 +84,41 @@ Puzzles.dmg: Puzzles
# be built on a regular basis.
nullgame : [X] gtk COMMON nullgame
nullgame : [G] WINDOWS COMMON nullgame
+
+# Version management.
+!begin vc
+version.obj: *.c *.h
+ cl $(VER) $(CFLAGS) /c version.c
+!end
+!specialobj vc version
+!begin cygwin
+version.o: FORCE
+FORCE:
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c version.c
+!end
+!specialobj cygwin version
+# For Unix, we also need the gross MD5 hack that causes automatic
+# version number selection in release source archives.
+!begin gtk
+version.o: FORCE;
+FORCE:
+ if test -z "$(VER)" && md5sum -c manifest; then \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat version.def` -c version.c; \
+ else \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c version.c; \
+ fi
+!end
+!specialobj gtk version
+# For OS X, this is made more fiddly by the fact that we don't have
+# md5sum readily available. We do, however, have `md5 -r' which
+# generates _nearly_ the same output, but it has no check function.
+!begin osx
+version.o: FORCE;
+FORCE:
+ if test -z "$(VER)" && test -f manifest && (md5 -r `awk '{print $$2}' manifest` | diff -w manifest -); then \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) `cat version.def` -c version.c; \
+ else \
+ $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) $(VER) -c version.c; \
+ fi
+!end
+!specialobj osx version
diff --git a/gtk.c b/gtk.c
index 199225a..5cd6832 100644
--- a/gtk.c
+++ b/gtk.c
@@ -525,7 +525,7 @@ static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
return FALSE;
}
-void error_box(GtkWidget *parent, char *msg)
+void message_box(GtkWidget *parent, char *title, char *msg, int centre)
{
GtkWidget *window, *hbox, *text, *ok;
@@ -538,7 +538,7 @@ void error_box(GtkWidget *parent, char *msg)
hbox, FALSE, FALSE, 20);
gtk_widget_show(text);
gtk_widget_show(hbox);
- gtk_window_set_title(GTK_WINDOW(window), "Error");
+ gtk_window_set_title(GTK_WINDOW(window), title);
gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
ok = gtk_button_new_with_label("OK");
gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
@@ -559,6 +559,11 @@ void error_box(GtkWidget *parent, char *msg)
gtk_main();
}
+void error_box(GtkWidget *parent, char *msg)
+{
+ message_box(parent, "Error", msg, FALSE);
+}
+
static void config_ok_button_clicked(GtkButton *button, gpointer data)
{
frontend *fe = (frontend *)data;
@@ -949,6 +954,21 @@ static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
fe->h = y;
}
+static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
+{
+ frontend *fe = (frontend *)data;
+ char titlebuf[256];
+ char textbuf[1024];
+
+ sprintf(titlebuf, "About %.200s", thegame.name);
+ sprintf(textbuf,
+ "%.200s\n\n"
+ "from Simon Tatham's Portable Puzzle Collection\n\n"
+ "%.500s", thegame.name, ver);
+
+ message_box(fe->window, titlebuf, textbuf, TRUE);
+}
+
static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont,
char *text, int key)
{
@@ -1080,6 +1100,19 @@ static frontend *new_window(char *game_id, char **error)
add_menu_separator(GTK_CONTAINER(menu));
add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
+ menuitem = gtk_menu_item_new_with_label("Help");
+ gtk_container_add(GTK_CONTAINER(menubar), menuitem);
+ gtk_widget_show(menuitem);
+
+ menu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+ menuitem = gtk_menu_item_new_with_label("About");
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+ GTK_SIGNAL_FUNC(menu_about_event), fe);
+ gtk_widget_show(menuitem);
+
{
int i, ncolours;
float *colours;
diff --git a/mkfiles.pl b/mkfiles.pl
index f5a3d9b..b805dbe 100755
--- a/mkfiles.pl
+++ b/mkfiles.pl
@@ -51,6 +51,7 @@ while (<IN>) {
if ($_[0] eq "!name") { $project_name = $_[1]; next; }
if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; }
if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;}
+ if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;}
if ($_[0] eq "!begin") {
if (&mfval($_[1])) {
$divert = \$makefile_extra{$_[1]};
@@ -299,6 +300,7 @@ sub deps {
@ret = ();
$depchar ||= ':';
foreach $i (sort keys %depends) {
+ next if $specialobj{$mftyp}->{$i};
if ($i =~ /^(.*)\.(res|rsrc)/) {
next if !defined $rtmpl;
$y = $1;
diff --git a/osx.m b/osx.m
index d4dd90a..11436a8 100644
--- a/osx.m
+++ b/osx.m
@@ -233,6 +233,98 @@ NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
}
/* ----------------------------------------------------------------------
+ * About box.
+ */
+
+@class AboutBox;
+
+@interface AboutBox : NSWindow
+{
+}
+- (id)init;
+@end
+
+@implementation AboutBox
+- (id)init
+{
+ NSRect totalrect;
+ NSView *views[16];
+ int nviews = 0;
+ NSImageView *iv;
+ NSTextField *tf;
+ NSFont *font1 = [NSFont systemFontOfSize:0];
+ NSFont *font2 = [NSFont boldSystemFontOfSize:[font1 pointSize] * 1.1];
+ const int border = 24;
+ int i;
+ double y;
+
+ /*
+ * Construct the controls that go in the About box.
+ */
+
+ iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0,0,64,64)];
+ [iv setImage:[NSImage imageNamed:@"NSApplicationIcon"]];
+ views[nviews++] = iv;
+
+ tf = [[NSTextField alloc]
+ initWithFrame:NSMakeRect(0,0,400,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setFont:font2];
+ [tf setStringValue:@"Simon Tatham's Portable Puzzle Collection"];
+ [tf sizeToFit];
+ views[nviews++] = tf;
+
+ tf = [[NSTextField alloc]
+ initWithFrame:NSMakeRect(0,0,400,1)];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setBordered:NO];
+ [tf setDrawsBackground:NO];
+ [tf setFont:font1];
+ [tf setStringValue:[NSString stringWithCString:ver]];
+ [tf sizeToFit];
+ views[nviews++] = tf;
+
+ /*
+ * Lay the controls out.
+ */
+ totalrect = NSMakeRect(0,0,0,0);
+ for (i = 0; i < nviews; i++) {
+ NSRect r = [views[i] frame];
+ if (totalrect.size.width < r.size.width)
+ totalrect.size.width = r.size.width;
+ totalrect.size.height += border + r.size.height;
+ }
+ totalrect.size.width += 2 * border;
+ totalrect.size.height += border;
+ y = totalrect.size.height;
+ for (i = 0; i < nviews; i++) {
+ NSRect r = [views[i] frame];
+ r.origin.x = (totalrect.size.width - r.size.width) / 2;
+ y -= border + r.size.height;
+ r.origin.y = y;
+ [views[i] setFrame:r];
+ }
+
+ self = [super initWithContentRect:totalrect
+ styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+ NSClosableWindowMask)
+ backing:NSBackingStoreBuffered
+ defer:YES];
+
+ for (i = 0; i < nviews; i++)
+ [[self contentView] addSubview:views[i]];
+
+ [self center]; /* :-) */
+
+ return self;
+}
+@end
+
+/* ----------------------------------------------------------------------
* The front end presented to midend.c.
*
* This is mostly a subclass of NSWindow. The actual `frontend'
@@ -1169,6 +1261,7 @@ void status_bar(frontend *fe, char *text)
{
}
- (void)newGameWindow:(id)sender;
+- (void)about:(id)sender;
@end
@implementation AppController
@@ -1182,6 +1275,14 @@ void status_bar(frontend *fe, char *text)
[win makeKeyAndOrderFront:self];
}
+- (void)about:(id)sender
+{
+ id win;
+
+ win = [[AboutBox alloc] init];
+ [win makeKeyAndOrderFront:self];
+}
+
- (NSMenu *)applicationDockMenu:(NSApplication *)sender
{
NSMenu *menu = newmenu("Dock Menu");
@@ -1224,6 +1325,8 @@ int main(int argc, char **argv)
[NSApp setMainMenu: newmenu("Main Menu")];
menu = newsubmenu([NSApp mainMenu], "Apple Menu");
+ item = newitem(menu, "About Puzzles", "", NULL, @selector(about:));
+ [menu addItem:[NSMenuItem separatorItem]];
[NSApp setServicesMenu:newsubmenu(menu, "Services")];
[menu addItem:[NSMenuItem separatorItem]];
item = newitem(menu, "Hide Puzzles", "h", NSApp, @selector(hide:));
diff --git a/puzzles.h b/puzzles.h
index 3867c2d..63de880 100644
--- a/puzzles.h
+++ b/puzzles.h
@@ -162,6 +162,11 @@ char *dupstr(const char *s);
void free_cfg(config_item *cfg);
/*
+ * version.c
+ */
+extern char ver[];
+
+/*
* random.c
*/
random_state *random_init(char *seed, int len);
diff --git a/version.c b/version.c
new file mode 100644
index 0000000..a44fbf6
--- /dev/null
+++ b/version.c
@@ -0,0 +1,16 @@
+/*
+ * Puzzles version numbering.
+ */
+
+#define STR1(x) #x
+#define STR(x) STR1(x)
+
+#if defined REVISION
+
+char ver[] = "Revision: r" STR(REVISION);
+
+#else
+
+char ver[] = "Unidentified build, " __DATE__ " " __TIME__;
+
+#endif
diff --git a/windows.c b/windows.c
index 414c7cc..feee3cf 100644
--- a/windows.c
+++ b/windows.c
@@ -35,6 +35,7 @@
#define IDM_HELPC 0x00A0
#define IDM_GAMEHELP 0x00B0
#define IDM_PRESETS 0x0100
+#define IDM_ABOUT 0x0110
#define HELP_FILE_NAME "puzzles.hlp"
#define HELP_CNT_NAME "puzzles.cnt"
@@ -112,7 +113,7 @@ struct frontend {
int nfonts, fontsize;
config_item *cfg;
struct cfg_aux *cfgaux;
- int cfg_which, cfg_done;
+ int cfg_which, dlg_done;
HFONT cfgfont;
char *help_path;
int help_has_contents;
@@ -517,16 +518,18 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
}
AppendMenu(menu, MF_SEPARATOR, 0, 0);
AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
+ menu = CreateMenu();
+ AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Help");
+ AppendMenu(menu, MF_ENABLED, IDM_ABOUT, "About");
if (fe->help_path) {
- HMENU hmenu = CreateMenu();
- AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)hmenu, "Help");
- AppendMenu(hmenu, MF_ENABLED, IDM_HELPC, "Contents");
+ AppendMenu(menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu, MF_ENABLED, IDM_HELPC, "Contents");
if (thegame.winhelp_topic) {
char *item;
assert(thegame.name);
item = snewn(9+strlen(thegame.name), char); /*ick*/
sprintf(item, "Help on %s", thegame.name);
- AppendMenu(hmenu, MF_ENABLED, IDM_GAMEHELP, item);
+ AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item);
sfree(item);
}
}
@@ -562,6 +565,30 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
return fe;
}
+static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ return 0;
+
+ case WM_COMMAND:
+ if ((HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) &&
+ LOWORD(wParam) == IDOK)
+ fe->dlg_done = 1;
+ return 0;
+
+ case WM_CLOSE:
+ fe->dlg_done = 1;
+ return 0;
+ }
+
+ return 0;
+}
+
static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
@@ -587,10 +614,10 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
MessageBox(hwnd, err, "Validation error",
MB_ICONERROR | MB_OK);
} else {
- fe->cfg_done = 2;
+ fe->dlg_done = 2;
}
} else {
- fe->cfg_done = 1;
+ fe->dlg_done = 1;
}
return 0;
}
@@ -624,7 +651,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
return 0;
case WM_CLOSE:
- fe->cfg_done = 1;
+ fe->dlg_done = 1;
return 0;
}
@@ -633,7 +660,7 @@ static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
char *wclass, int wstyle,
- int exstyle, char *wtext, int wid)
+ int exstyle, const char *wtext, int wid)
{
HWND ret;
ret = CreateWindowEx(exstyle, wclass, wtext,
@@ -643,6 +670,158 @@ HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
return ret;
}
+static void about(frontend *fe)
+{
+ int i;
+ WNDCLASS wc;
+ MSG msg;
+ TEXTMETRIC tm;
+ HDC hdc;
+ HFONT oldfont;
+ SIZE size;
+ int gm, id;
+ int winwidth, winheight, y;
+ int height, width, maxwid;
+ const char *strings[16];
+ int lengths[16];
+ int nstrings = 0;
+ char titlebuf[512];
+
+ sprintf(titlebuf, "About %.250s", thegame.name);
+
+ strings[nstrings++] = thegame.name;
+ strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection";
+ strings[nstrings++] = ver;
+
+ wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
+ wc.lpfnWndProc = DefDlgProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = DLGWINDOWEXTRA + 8;
+ wc.hInstance = fe->inst;
+ wc.hIcon = NULL;
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = "GameAboutBox";
+ RegisterClass(&wc);
+
+ hdc = GetDC(fe->hwnd);
+ SetMapMode(hdc, MM_TEXT);
+
+ fe->dlg_done = FALSE;
+
+ fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
+ 0, 0, 0, 0,
+ FALSE, FALSE, FALSE, DEFAULT_CHARSET,
+ OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
+ DEFAULT_QUALITY,
+ FF_SWISS,
+ "MS Shell Dlg");
+
+ oldfont = SelectObject(hdc, fe->cfgfont);
+ if (GetTextMetrics(hdc, &tm)) {
+ height = tm.tmAscent + tm.tmDescent;
+ width = tm.tmAveCharWidth;
+ } else {
+ height = width = 30;
+ }
+
+ /*
+ * Figure out the layout of the About box by measuring the
+ * length of each piece of text.
+ */
+ maxwid = 0;
+ winheight = height/2;
+
+ for (i = 0; i < nstrings; i++) {
+ if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size))
+ lengths[i] = size.cx;
+ else
+ lengths[i] = 0; /* *shrug* */
+ if (maxwid < lengths[i])
+ maxwid = lengths[i];
+ winheight += height * 3 / 2 + (height / 2);
+ }
+
+ winheight += height + height * 7 / 4; /* OK button */
+ winwidth = maxwid + 4*width;
+
+ SelectObject(hdc, oldfont);
+ ReleaseDC(fe->hwnd, hdc);
+
+ /*
+ * Create the dialog, now that we know its size.
+ */
+ {
+ RECT r, r2;
+
+ r.left = r.top = 0;
+ r.right = winwidth;
+ r.bottom = winheight;
+
+ AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
+ DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+ WS_CAPTION | WS_SYSMENU*/) &~
+ (WS_MAXIMIZEBOX | WS_OVERLAPPED),
+ FALSE, 0);
+
+ /*
+ * Centre the dialog on its parent window.
+ */
+ r.right -= r.left;
+ r.bottom -= r.top;
+ GetWindowRect(fe->hwnd, &r2);
+ r.left = (r2.left + r2.right - r.right) / 2;
+ r.top = (r2.top + r2.bottom - r.bottom) / 2;
+ r.right += r.left;
+ r.bottom += r.top;
+
+ fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf,
+ DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+ WS_CAPTION | WS_SYSMENU,
+ r.left, r.top,
+ r.right-r.left, r.bottom-r.top,
+ fe->hwnd, NULL, fe->inst, NULL);
+ }
+
+ SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
+
+ SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
+ SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc);
+
+ id = 1000;
+ y = height/2;
+ for (i = 0; i < nstrings; i++) {
+ int border = width*2 + (maxwid - lengths[i]) / 2;
+ mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8,
+ "Static", 0, 0, strings[i], id++);
+ y += height*3/2;
+
+ assert(y < winheight);
+ y += height/2;
+ }
+
+ y += height/2; /* extra space before OK */
+ mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON",
+ BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
+ "OK", IDOK);
+
+ SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
+
+ EnableWindow(fe->hwnd, FALSE);
+ ShowWindow(fe->cfgbox, SW_NORMAL);
+ while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
+ if (!IsDialogMessage(fe->cfgbox, &msg))
+ DispatchMessage(&msg);
+ if (fe->dlg_done)
+ break;
+ }
+ EnableWindow(fe->hwnd, TRUE);
+ SetForegroundWindow(fe->hwnd);
+ DestroyWindow(fe->cfgbox);
+ DeleteObject(fe->cfgfont);
+}
+
static int get_config(frontend *fe, int which)
{
config_item *i;
@@ -674,7 +853,7 @@ static int get_config(frontend *fe, int which)
hdc = GetDC(fe->hwnd);
SetMapMode(hdc, MM_TEXT);
- fe->cfg_done = FALSE;
+ fe->dlg_done = FALSE;
fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
0, 0, 0, 0,
@@ -738,6 +917,7 @@ static int get_config(frontend *fe, int which)
col2r = col1l+2*height+maxcheckbox;
winwidth = col2r + 2*width;
+ SelectObject(hdc, oldfont);
ReleaseDC(fe->hwnd, hdc);
/*
@@ -869,7 +1049,7 @@ static int get_config(frontend *fe, int which)
while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
if (!IsDialogMessage(fe->cfgbox, &msg))
DispatchMessage(&msg);
- if (fe->cfg_done)
+ if (fe->dlg_done)
break;
}
EnableWindow(fe->hwnd, TRUE);
@@ -880,7 +1060,7 @@ static int get_config(frontend *fe, int which)
free_cfg(fe->cfg);
sfree(fe->cfgaux);
- return (fe->cfg_done == 2);
+ return (fe->dlg_done == 2);
}
static void new_game_type(frontend *fe)
@@ -979,6 +1159,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
if (get_config(fe, CFG_SEED))
new_game_type(fe);
break;
+ case IDM_ABOUT:
+ about(fe);
+ break;
case IDM_HELPC:
assert(fe->help_path);
WinHelp(hwnd, fe->help_path,