Check-in [007d0a9b3f]
Not logged in
Overview

SHA1 Hash:007d0a9b3f7c0288c072cc4909571b50d2e54392
Date: 2009-12-19 21:04:29
User: drh
Comment:Add a new "Skins" configuration page that allows the look and feel of the web interface to be changed to one of several prototypes at the click of a button. Currently, there are only two built-in prototypes.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/main.mk from [08be0ee0ca] to [0f9d767d5d].

@@ -60,10 +60,11 @@
   $(SRCDIR)/schema.c \
   $(SRCDIR)/search.c \
   $(SRCDIR)/setup.c \
   $(SRCDIR)/sha1.c \
   $(SRCDIR)/shun.c \
+  $(SRCDIR)/skins.c \
   $(SRCDIR)/stat.c \
   $(SRCDIR)/style.c \
   $(SRCDIR)/sync.c \
   $(SRCDIR)/tag.c \
   $(SRCDIR)/th_main.c \
@@ -130,10 +131,11 @@
   schema_.c \
   search_.c \
   setup_.c \
   sha1_.c \
   shun_.c \
+  skins_.c \
   stat_.c \
   style_.c \
   sync_.c \
   tag_.c \
   th_main_.c \
@@ -200,10 +202,11 @@
   schema.o \
   search.o \
   setup.o \
   sha1.o \
   shun.o \
+  skins.o \
   stat.o \
   style.o \
   sync.o \
   tag.o \
   th_main.o \
@@ -261,16 +264,16 @@
 	# noop
 
 clean:
 	rm -f *.o *_.c $(APPNAME) VERSION.h
 	rm -f translate makeheaders mkindex page_index.h headers
-	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
+	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
 
 page_index.h: $(TRANS_SRC) mkindex
 	./mkindex $(TRANS_SRC) >$@
 headers:	page_index.h makeheaders VERSION.h
-	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
+	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
 	touch headers
 headers: Makefile
 Makefile:
 add_.c:	$(SRCDIR)/add.c translate
 	./translate $(SRCDIR)/add.c >add_.c
@@ -613,10 +616,17 @@
 
 shun.o:	shun_.c shun.h  $(SRCDIR)/config.h
 	$(XTCC) -o shun.o -c shun_.c
 
 shun.h:	headers
+skins_.c:	$(SRCDIR)/skins.c translate
+	./translate $(SRCDIR)/skins.c >skins_.c
+
+skins.o:	skins_.c skins.h  $(SRCDIR)/config.h
+	$(XTCC) -o skins.o -c skins_.c
+
+skins.h:	headers
 stat_.c:	$(SRCDIR)/stat.c translate
 	./translate $(SRCDIR)/stat.c >stat_.c
 
 stat.o:	stat_.c stat.h  $(SRCDIR)/config.h
 	$(XTCC) -o stat.o -c stat_.c

Modified src/makemake.tcl from [8696139730] to [1776157a16].

@@ -54,10 +54,11 @@
   schema
   search
   setup
   sha1
   shun
+  skins
   stat
   style
   sync
   tag
   th_main

Modified src/setup.c from [d61571ef37] to [7bbb5e4563].

@@ -70,10 +70,12 @@
     "Configure the SCM behavior of the repository");
   setup_menu_entry("Timeline", "setup_timeline",
     "Timeline display preferences");
   setup_menu_entry("Tickets", "tktsetup",
     "Configure the trouble-ticketing system for this repository");
+  setup_menu_entry("Skins", "setup_skin",
+    "Select from a menu of prepackaged \"skins\" for the web interface");
   setup_menu_entry("CSS", "setup_editcss",
     "Edit the Cascading Style Sheet used by all pages of this repository");
   setup_menu_entry("Header", "setup_header",
     "Edit HTML text inserted at the top of every page");
   setup_menu_entry("Footer", "setup_footer",
@@ -723,10 +725,19 @@
   @ from the ~/.fossil database. Password login is always required
   @ for incoming web connections on internet addresses other than
   @ 127.0.0.1.</p></li>
 
   @ <hr>
+  onoff_attribute("Show javascript button to fill in CAPTCHA",
+                  "auto-captcha", "autocaptcha", 0);
+  @ <p>When enabled, a button appears on the login screen for user
+  @ "anonymous" that will automatically fill in the CAPTCHA password.
+  @ This is less secure that forcing the user to do it manually, but is
+  @ probably secure enough and it is certainly more convenient for
+  @ anonymous users.</p>
+
+  @ <hr>
   entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
   @ <p>The number of hours for which a login is valid.  This must be a
   @ positive number.  The default is 8760 hours which is approximately equal
   @ to a year.</p>
 
@@ -816,11 +827,12 @@
   onoff_attribute("Show javascript button to fill in CAPTCHA",
                   "auto-captcha", "autocaptcha", 0);
   @ <p>When enabled, a button appears on the login screen for user
   @ "anonymous" that will automatically fill in the CAPTCHA password.
   @ This is less secure that forcing the user to do it manually, but is
-  @ usually secure enough.</p>
+  @ probably secure enough and it is certainly more convenient for
+  @ anonymous users.</p>
 
   @ <hr>
   onoff_attribute("Sign all commits with GPG",
                   "clearsign", "clearsign", 1);
   @ <p>When enabled (the default), fossil will attempt to
@@ -943,18 +955,19 @@
     textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS);
   }
   style_header("Edit CSS");
   @ <form action="%s(g.zBaseURL)/setup_editcss" method="POST">
   login_insert_csrf_secret();
-  @ Edit the CSS:<br />
+  @ Edit the CSS below:<br />
   textarea_attribute("", 40, 80, "css", "css", zDefaultCSS);
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default CSS:
+  @ The default CSS is shown below for reference.  Other examples
+  @ of CSS files can be seen on the <a href="setup_skin">skins page</a>.
   @ <blockquote><pre>
   @ %h(zDefaultCSS)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -985,11 +998,12 @@
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default page header:
+  @ The default header is shown below for reference.  Other examples
+  @ of headers can be seen on the <a href="setup_skin">skins page</a>.
   @ <blockquote><pre>
   @ %h(zDefaultHeader)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -1019,11 +1033,12 @@
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default page footer:
+  @ The default footer is shown below for reference.  Other examples
+  @ of footers can be seen on the <a href="setup_skin">skins page</a>.
   @ <blockquote><pre>
   @ %h(zDefaultFooter)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -1067,15 +1082,19 @@
   style_header("Edit Project Logo");
   @ <p>The current project logo has a MIME-Type of <b>%h(zMime)</b> and looks
   @ like this:</p>
   @ <blockquote><img src="%s(g.zTop)/logo" alt="logo"></blockquote>
   @
-  @ <form action="%s(g.zBaseURL)/setup_logo" method="POST"
-  @  enctype="multipart/form-data">
   @ <p>The logo is accessible to all users at this URL:
   @ <a href="%s(g.zBaseURL)/logo">%s(g.zBaseURL)/logo</a>.
-  @ To set a new logo image, select a file to use as the logo using
+  @ The logo may or may not appear on each
+  @ page depending on the <a href="setup_editcss">CSS</a> and
+  @ <a href="setup_header">header setup</a>.</p>
+  @
+  @ <form action="%s(g.zBaseURL)/setup_logo" method="POST"
+  @  enctype="multipart/form-data">
+  @ <p>To set a new logo image, select a file to use as the logo using
   @ the entry box below and then press the "Change Logo" button.</p>
   login_insert_csrf_secret();
   @ Logo Image file:
   @ <input type="file" name="im" size="60" accepts="image/*"><br>
   @ <input type="submit" name="set" value="Change Logo">

Added src/skins.c version [4b8e7e2c10]

@@ -1,1 +1,410 @@
+/*
+** Copyright (c) 2009 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   drh@hwaci.com
+**   http://www.hwaci.com/drh/
+**
+*******************************************************************************
+**
+** Implementation of the Setup page for "skins".
+*/
+#include <assert.h>
+#include "config.h"
+#include "skins.h"
+
+/*
+** A black-and-white theme with the project title in a bar across the top
+** and no logo image.
+*/
+static const char zBuiltinSkin1[] =
+@ REPLACE INTO config VALUES('css','/* General settings for the entire page */
+@ body {
+@   margin: 0ex 1ex;
+@   padding: 0px;
+@   background-color: white;
+@   font-family: "sans serif";
+@ }
+@
+@ /* The project logo in the upper left-hand corner of each page */
+@ div.logo {
+@   display: table-row;
+@   text-align: center;
+@   /* vertical-align: bottom;*/
+@   font-size: 2em;
+@   font-weight: bold;
+@   background-color: #707070;
+@   color: #ffffff;
+@ }
+@
+@ /* The page title centered at the top of each page */
+@ div.title {
+@   display: table-cell;
+@   font-size: 1.5em;
+@   font-weight: bold;
+@   text-align: left;
+@   padding: 0 0 0 10px;
+@   color: #404040;
+@   vertical-align: bottom;
+@   width: 100%;
+@ }
+@
+@ /* The login status message in the top right-hand corner */
+@ div.status {
+@   display: table-cell;
+@   text-align: right;
+@   vertical-align: bottom;
+@   color: #404040;
+@   font-size: 0.8em;
+@   font-weight: bold;
+@ }
+@
+@ /* The header across the top of the page */
+@ div.header {
+@   display: table;
+@   width: 100%;
+@ }
+@
+@ /* The main menu bar that appears at the top of the page beneath
+@ ** the header */
+@ div.mainmenu {
+@   padding: 5px 10px 5px 10px;
+@   font-size: 0.9em;
+@   font-weight: bold;
+@   text-align: center;
+@   letter-spacing: 1px;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The submenu bar that *sometimes* appears below the main menu */
+@ div.submenu {
+@   padding: 3px 10px 3px 0px;
+@   font-size: 0.9em;
+@   text-align: center;
+@   background-color: #606060;
+@   color: white;
+@ }
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
+@   padding: 3px 10px 3px 10px;
+@   color: white;
+@   text-decoration: none;
+@ }
+@ div.mainmenu a:hover, div.submenu a:hover {
+@   color: #404040;
+@   background-color: white;
+@ }
+@
+@ /* All page content from the bottom of the menu or submenu down to
+@ ** the footer */
+@ div.content {
+@   padding: 0ex 0ex 0ex 0ex;
+@ }
+@ /* Hyperlink colors */
+@ div.content a { color: #604000; }
+@ div.content a:link { color: #604000;}
+@ div.content a:visited { color: #600000; }
+@
+@ /* Some pages have section dividers */
+@ div.section {
+@   margin-bottom: 0px;
+@   margin-top: 1em;
+@   padding: 1px 1px 1px 1px;
+@   font-size: 1.2em;
+@   font-weight: bold;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The "Date" that occurs on the left hand side of timelines */
+@ div.divider {
+@   background: #a0a0a0;
+@   border: 2px #505050 solid;
+@   font-size: 1em; font-weight: normal;
+@   padding: .25em;
+@   margin: .2em 0 .2em 0;
+@   float: left;
+@   clear: left;
+@ }
+@
+@ /* The footer at the very bottom of the page */
+@ div.footer {
+@   font-size: 0.8em;
+@   margin-top: 12px;
+@   padding: 5px 10px 5px 10px;
+@   text-align: right;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The label/value pairs on (for example) the vinfo page */
+@ table.label-value th {
+@   vertical-align: top;
+@   text-align: right;
+@   padding: 0.2ex 2ex;
+@ }');
+@ REPLACE INTO config VALUES('header','<html>
+@ <head>
+@ <title>$<project_name>: $<title></title>
+@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
+@       href="$baseurl/timeline.rss">
+@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
+@       media="screen">
+@ </head>
+@ <body>
+@ <div class="header">
+@   <div class="logo">
+@     <nobr>$<project_name></nobr>
+@   </div>
+@ </div>
+@ <div class="header">
+@   <div class="title">$<title></div>
+@   <div class="status"><nobr><th1>
+@      if {[info exists login]} {
+@        puts "Logged in as $login"
+@      } else {
+@        puts "Not logged in"
+@      }
+@   </th1></nobr></div>
+@ </div>
+@ <div class="mainmenu"><th1>
+@ html "<a href=''$baseurl$index_page''>Home</a> "
+@ if {[hascap h]} {
+@   html "<a href=''$baseurl/dir''>Files</a> "
+@ }
+@ if {[hascap o]} {
+@   html "<a href=''$baseurl/leaves''>Leaves</a> "
+@   html "<a href=''$baseurl/timeline''>Timeline</a> "
+@   html "<a href=''$baseurl/brlist''>Branches</a> "
+@   html "<a href=''$baseurl/taglist''>Tags</a> "
+@ }
+@ if {[hascap r]} {
+@   html "<a href=''$baseurl/reportlist''>Tickets</a> "
+@ }
+@ if {[hascap j]} {
+@   html "<a href=''$baseurl/wiki''>Wiki</a> "
+@ }
+@ if {[hascap s]} {
+@   html "<a href=''$baseurl/setup''>Admin</a> "
+@ } elseif {[hascap a]} {
+@   html "<a href=''$baseurl/setup_ulist''>Users</a> "
+@ }
+@ if {[info exists login]} {
+@   html "<a href=''$baseurl/login''>Logout</a> "
+@ } else {
+@   html "<a href=''$baseurl/login''>Login</a> "
+@ }
+@ </th1></div>
+@ ');
+@ REPLACE INTO config VALUES('footer','<div class="footer">
+@ Fossil version $manifest_version $manifest_date
+@ </div>
+@ </body></html>
+@ ');
+;
+
+/*
+** An array of available built-in skins.
+*/
+static struct BuiltinSkin {
+  const char *zName;
+  const char *zValue;
+} aBuiltinSkin[] = {
+  { "Default",              0 /* Filled in at runtime */ },
+  { "Plain Gray, No Logo",  zBuiltinSkin1                },
+};
+
+/*
+** For a skin named zSkinName, compute the name of the CONFIG table
+** entry where that skin is stored and return it.
+**
+** Return NULL if zSkinName is NULL or an empty string.
+**
+** If ifExists is true, and the named skin does not exist, return NULL.
+*/
+static char *skinVarName(const char *zSkinName, int ifExists){
+  char *z;
+  if( zSkinName==0 || zSkinName[0]==0 ) return 0;
+  z = mprintf("skin:%s", zSkinName);
+  if( ifExists && !db_exists("SELECT 1 FROM config WHERE name=%Q", z) ){
+    free(z);
+    z = 0;
+  }
+  return z;
+}
+
+/*
+** Construct and return a string that represents the current skin if
+** useDefault==0 or a string for the default skin if useDefault==1.
+**
+** Memory to hold the returned string is obtained from malloc.
+*/
+static char *getSkin(int useDefault){
+  Blob val;
+  blob_zero(&val);
+  blob_appendf(&val, "REPLACE INTO config VALUES('css',%Q);\n",
+     useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS)
+  );
+  blob_appendf(&val, "REPLACE INTO config VALUES('header',%Q);\n",
+     useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader)
+  );
+  blob_appendf(&val, "REPLACE INTO config VALUES('footer',%Q);\n",
+     useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter)
+  );
+  return blob_str(&val);
+}
+
+/*
+** Construct the default skin string and fill in the corresponding
+** entry in aBuildinSkin[]
+*/
+static void setDefaultSkin(void){
+  aBuiltinSkin[0].zValue = getSkin(1);
+}
+
+/*
+** WEBPAGE: setup_skin
+*/
+void setup_skin(void){
+  const char *z;
+  char *zName;
+  char *zErr = 0;
+  const char *zCurrent;  /* Current skin */
+  int i;                 /* Loop counter */
+  Stmt q;
+
+  login_check_credentials();
+  if( !g.okSetup ){
+    login_needed();
+  }
+  db_begin_transaction();
+
+  /* Process requests to delete a user-defined skin */
+  if( P("del1") && (zName = skinVarName(P("sn"), 1))!=0 ){
+    style_header("Confirm Custom Skin Delete");
+    @ <form action="%s(g.zBaseURL)/setup_skin" method="POST">
+    @ <p>Deletion of a custom skin is a permanent action that cannot
+    @ be undone.  Please confirm that this is what you want to do:</p>
+    @ <input type="hidden" name="sn" value="%h(P("sn"))">
+    @ <input type="submit" name="del2" value="Confirm - Delete The Skin">
+    @ <input type="submit" name="cancel" value="Cancel - Do Not Delete">
+    login_insert_csrf_secret();
+    @ </form>
+    style_footer();
+    return;
+  }
+  if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
+    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
+  }
+
+  setDefaultSkin();
+  zCurrent = getSkin(0);
+
+  if( P("save")!=0 && (zName = skinVarName(P("save"),0))!=0 ){
+    if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName)
+          || strcmp(zName, "Default")==0 ){
+      zErr = mprintf("Skin name \"%h\" already exists. "
+                     "Choose a different name.", P("sn"));
+    }else{
+      db_multi_exec("INSERT INTO config VALUES(%Q,%Q)",
+         zName, zCurrent
+      );
+    }
+  }
+
+  /* The user pressed the "Use This Skin" button. */
+  if( P("load") && (z = P("sn"))!=0 && z[0] ){
+    int seen = 0;
+    for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+      if( strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){
+        seen = 1;
+        break;
+      }
+    }
+    if( !seen ){
+      seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
+                       " AND value=%Q", zCurrent);
+    }
+    if( !seen ){
+      db_multi_exec(
+        "INSERT INTO config VALUES("
+        "  strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
+        "  %Q)", zCurrent
+      );
+    }
+    seen = 0;
+    for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+      if( strcmp(aBuiltinSkin[i].zName, z)==0 ){
+        seen = 1;
+        zCurrent = aBuiltinSkin[i].zValue;
+        db_multi_exec("%s", zCurrent);
+        break;
+      }
+    }
+    if( !seen ){
+      zName = skinVarName(z,0);
+      zCurrent = db_get(zName, 0);
+      db_multi_exec("%s", zCurrent);
+    }
+  }
 
+  style_header("Skins");
+  @ <p>A "skin" is a combination of
+  @ <a href="setup_editcss">CSS</a>,
+  @ <a href="setup_header">Header</a>, and
+  @ <a href="setup_footer">Footer</a> that determines the look and feel
+  @ of the web interface.</p>
+  @
+  @ <h2>Available Skins:</h2>
+  @ <ol>
+  for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+    z = aBuiltinSkin[i].zName;
+    if( strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){
+      @ <li><p>%h(z).&nbsp;&nbsp; <b>Currently In Use</b></p>
+    }else{
+      @ <li><form action="%s(g.zBaseURL)/setup_skin" method="POST">
+      @ %h(z).&nbsp;&nbsp;
+      @ <input type="hidden" name="sn" value="%h(z)">
+      @ <input type="submit" name="load" value="Use This Skin">
+      @ </form></li>
+    }
+  }
+  db_prepare(&q,
+     "SELECT substr(name, 6), value FROM config"
+     " WHERE name GLOB 'skin:*'"
+     " ORDER BY name"
+  );
+  while( db_step(&q)==SQLITE_ROW ){
+    const char *zN = db_column_text(&q, 0);
+    const char *zV = db_column_text(&q, 1);
+    if( strcmp(zV, zCurrent)==0 ){
+      @ <li><p>%h(zN) <b>Currently In Use</b></p>
+    }else{
+      @ <li><form action="%s(g.zBaseURL)/setup_skin" method="POST">
+      @ %h(zN).&nbsp;&nbsp;
+      @ <input type="hidden" name="sn" value="%h(zN)">
+      @ <input type="submit" name="load" value="Use This Skin">
+      @ <input type="submit" name="del1" value="Delete This Skin">
+      @ </form></li>
+    }
+  }
+  db_finalize(&q);
+  @ </ol>
+  style_footer();
+  db_end_transaction(0);
+}

Modified src/style.c from [c7fc56d774] to [d8b310372d].

@@ -360,11 +360,11 @@
 @   text-align: right;
 @   background-color: #558195;
 @   color: white;
 @ }
 @
-@ /* Make the links in the footer less ugly... */
+@ /* Hyperlink colors */
 @ div.footer a { color: white; }
 @ div.footer a:link { color: white; }
 @ div.footer a:visited { color: white; }
 @ div.footer a:hover { background-color: white; color: #558195; }
 @
@@ -377,25 +377,10 @@
 @ /* The label/value pairs on (for example) the ci page */
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
-@ }
-@
-@ /* For marking important UI elements which shouldn't be
-@    lightly dismissed. I mainly use it to mark "not yet
-@    implemented" parts of a page. Whether or not to have
-@    a 'border' attribute set is arguable. */
-@ .achtung {
-@   color: #ff0000;
-@   background: #ffff00;
-@   border: 1px solid #ff0000;
-@ }
-@
-@ div.miniform {
-@     font-size: smaller;
-@     margin: 8px;
 @ }
 ;
 
 /*
 ** WEBPAGE: style.css