From d99b1ff360bc9f5f14a82e875c092cf9e2e02545 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 21 Nov 2002 06:09:12 +0000 Subject: [PATCH] handle larger, nested indexes --- pandaapp/src/indexify/fontSamples.cxx | 3 +- pandaapp/src/indexify/indexImage.cxx | 91 ++----- pandaapp/src/indexify/indexImage.h | 3 - pandaapp/src/indexify/indexParameters.cxx | 114 ++++++++ pandaapp/src/indexify/indexParameters.h | 8 + pandaapp/src/indexify/indexify.cxx | 129 +++++++-- pandaapp/src/indexify/rollDirectory.cxx | 302 ++++++++++++++++++++-- pandaapp/src/indexify/rollDirectory.h | 17 +- 8 files changed, 560 insertions(+), 107 deletions(-) diff --git a/pandaapp/src/indexify/fontSamples.cxx b/pandaapp/src/indexify/fontSamples.cxx index f6bfc09b49..52d77c1268 100644 --- a/pandaapp/src/indexify/fontSamples.cxx +++ b/pandaapp/src/indexify/fontSamples.cxx @@ -20,9 +20,10 @@ #include "pnmTextMaker.h" #include "default_font.h" #include "pnmImage.h" - #include "notify.h" +#include + //////////////////////////////////////////////////////////////////// // Function: FontSamples::Constructor // Access: Public diff --git a/pandaapp/src/indexify/indexImage.cxx b/pandaapp/src/indexify/indexImage.cxx index a13e7bbbfe..7b8a673c77 100644 --- a/pandaapp/src/indexify/indexImage.cxx +++ b/pandaapp/src/indexify/indexImage.cxx @@ -96,8 +96,8 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { PNMImage index_image; Filename reduced_dir(archive_dir, "reduced/" + _dir->get_basename()); - - Filename output_filename(archive_dir, _name); + Filename thumbnail_dir(archive_dir, "thumbs"); + Filename output_filename(thumbnail_dir, _name); output_filename.set_extension("jpg"); Photos::const_iterator pi; @@ -161,9 +161,11 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { Photo *photo = _dir->get_photo(pinfo._photo_index); Filename photo_filename(_dir->get_dir(), photo->get_basename()); Filename reduced_filename(reduced_dir, photo->get_basename()); + photo_filename.standardize(); + reduced_filename.standardize(); PNMImage reduced_image; - if (!dummy_mode && + if (!dummy_mode && photo_filename != reduced_filename && (force_regenerate || reduced_filename.compare_timestamps(photo_filename) < 0)) { // If the reduced filename does not exist or is older than the @@ -182,6 +184,10 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { // Generate a reduced image for the photo. compute_reduction(photo_image, reduced_image, reduced_width, reduced_height); + + photo->_reduced_x_size = reduced_image.get_x_size(); + photo->_reduced_y_size = reduced_image.get_y_size(); + reduced_image.quick_filter_from(photo_image); reduced_filename.make_dir(); nout << "Writing " << reduced_filename << "\n"; @@ -190,9 +196,6 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { return false; } - photo->_reduced_x_size = reduced_image.get_x_size(); - photo->_reduced_y_size = reduced_image.get_y_size(); - } else { // If the reduced image already exists and is newer than the // source image, use it. @@ -262,7 +265,8 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { pinfo._x_place + x_center, pinfo._y_place + y_center, thumbnail_image.get_x_size(), thumbnail_image.get_y_size()); } - + + thumbnail_image.set_color_type(index_image.get_color_type()); index_image.copy_sub_image(thumbnail_image, pinfo._x_place + x_center, pinfo._y_place + y_center); @@ -277,6 +281,7 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { if (generate_index_image) { nout << "Writing " << output_filename << "\n"; + output_filename.make_dir(); if (!index_image.write(output_filename)) { nout << "Unable to write.\n"; return false; @@ -304,21 +309,22 @@ generate_html(ostream &root_html, const Filename &archive_dir, const Filename &roll_dir_root) { root_html << "\n" - << "\n" << "
\n"; + Filename html_dir(archive_dir, "html"); + Photos::const_iterator pi; for (pi = _photos.begin(); pi != _photos.end(); ++pi) { const PhotoInfo &pinfo = (*pi); int photo_index = pinfo._photo_index; Photo *photo = _dir->get_photo(pinfo._photo_index); - Filename html_relname("html/" + _dir->get_basename(), - photo->get_basename()); + Filename html_relname(_dir->get_basename(), photo->get_basename()); html_relname.set_extension("htm"); - Filename html_filename(archive_dir, html_relname); + Filename html_filename(html_dir, html_relname); html_filename.make_dir(); html_filename.set_text(); ofstream reduced_html; @@ -382,15 +388,18 @@ write(ostream &out, int indent_level) const { bool IndexImage:: generate_reduced_html(ostream &html, Photo *photo, int photo_index, int pi, const Filename &roll_dir_root) { - Filename full_dir = + Filename full_dir; + if (roll_dir_root.empty()) { + full_dir = Filename("../..", _dir->get_dir()); + } else { compose_href("../..", roll_dir_root, _dir->get_basename()); + } Filename full(full_dir, photo->get_basename()); Filename reduced_dir("../../reduced", _dir->get_basename()); Filename reduced(reduced_dir, photo->get_basename()); - Filename root_html("../..", "index.htm"); - string up_href = root_html.get_fullpath() + "#" + _name; + string up_href = "../" + _dir->get_basename() + ".htm#" + _name; Filename prev_photo_filename; Filename next_photo_filename; @@ -485,7 +494,9 @@ generate_reduced_html(ostream &html, Photo *photo, int photo_index, int pi, << " height=" << photo->_reduced_y_size << " alt=\"" << photo->get_name() << "\">

\n"; - if (!omit_full_links) { + if (!omit_full_links && + (photo->_full_x_size != photo->_reduced_x_size || + photo->_full_y_size != photo->_reduced_y_size)) { html << "

View full size image (" << photo->_full_x_size << " x " << photo->_full_y_size << ")

"; @@ -503,7 +514,7 @@ generate_reduced_html(ostream &html, Photo *photo, int photo_index, int pi, //////////////////////////////////////////////////////////////////// // Function: IndexImage::generate_nav_buttons -// Access: Private, Static +// Access: Private // Description: Outputs the HTML code to generate the next, prev, // up buttons when viewing each reduced image. //////////////////////////////////////////////////////////////////// @@ -570,54 +581,6 @@ generate_nav_buttons(ostream &html, const Filename &prev_photo_filename, html << "

\n"; } -//////////////////////////////////////////////////////////////////// -// Function: IndexImage::compose_href -// Access: Private -// Description: Combines a user-supplied prefix with a relative -// directory to generate the appropriate href to the -// file. -// -// rel_dir is the relative path to archive_dir. -// user_prefix is the string the user indicated as the -// relative or absolute path to the file's parent -// directory from archive_dir. basename is the name -// of the file, or empty if the filename is part of -// user_prefix. -//////////////////////////////////////////////////////////////////// -Filename IndexImage:: -compose_href(const Filename &rel_dir, const Filename &user_prefix, - const Filename &basename) { - Filename result; - - if (user_prefix.empty()) { - result = rel_dir; - - } else { - // Check to see if the user prefix begins with a URL designator, - // like http:// or ftp://. - size_t ui = 0; - while (ui < user_prefix.length() && isalpha(user_prefix[ui])) { - ui++; - } - bool is_url = (user_prefix.get_fullpath().substr(ui, 3) == "://"); - - if (!is_url && user_prefix.is_local()) { - Filename rel_user_dir(rel_dir, user_prefix); - result = rel_user_dir; - result.standardize(); - - } else { - result = user_prefix; - } - } - - if (basename.empty()) { - return result; - } else { - return Filename(result, basename); - } -} - //////////////////////////////////////////////////////////////////// // Function: IndexImage::compute_reduction // Access: Private, Static diff --git a/pandaapp/src/indexify/indexImage.h b/pandaapp/src/indexify/indexImage.h index 21ff193ac0..0cb8ee2c28 100644 --- a/pandaapp/src/indexify/indexImage.h +++ b/pandaapp/src/indexify/indexImage.h @@ -56,9 +56,6 @@ private: const Filename &next_photo_filename, const string &up_href); - Filename compose_href(const Filename &rel_dir, const Filename &user_prefix, - const Filename &basename = Filename()); - static void compute_reduction(const PNMImageHeader &source_image, PNMImage &dest_image, int x_size, int y_size); diff --git a/pandaapp/src/indexify/indexParameters.cxx b/pandaapp/src/indexify/indexParameters.cxx index 12f3a4c24d..f53e85cee1 100644 --- a/pandaapp/src/indexify/indexParameters.cxx +++ b/pandaapp/src/indexify/indexParameters.cxx @@ -44,6 +44,7 @@ Filename up_icon; bool force_regenerate = false; bool format_rose = false; +bool sort_date = false; bool dummy_mode = false; bool draw_frames = false; bool omit_roll_headers = false; @@ -87,3 +88,116 @@ finalize_parameters() { thumb_interior_height = thumb_height; } } + +//////////////////////////////////////////////////////////////////// +// Function: compose_href +// Description: Combines a user-supplied prefix with a relative +// directory to generate the appropriate href to the +// file. +// +// rel_dir is the relative path to archive_dir. +// user_prefix is the string the user indicated as the +// relative or absolute path to the file's parent +// directory from archive_dir. basename is the name +// of the file, or empty if the filename is part of +// user_prefix. +//////////////////////////////////////////////////////////////////// +Filename +compose_href(const Filename &rel_dir, const Filename &user_prefix, + const Filename &basename) { + Filename result; + + if (user_prefix.empty()) { + result = rel_dir; + + } else { + // Check to see if the user prefix begins with a URL designator, + // like http:// or ftp://. + size_t ui = 0; + while (ui < user_prefix.length() && isalpha(user_prefix[ui])) { + ui++; + } + bool is_url = (user_prefix.get_fullpath().substr(ui, 3) == "://"); + + if (!is_url && user_prefix.is_local()) { + Filename rel_user_dir(rel_dir, user_prefix); + result = rel_user_dir; + result.standardize(); + + } else { + result = user_prefix; + } + } + + if (basename.empty()) { + return result; + } else { + return Filename(result, basename); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: escape_html +// Description: Returns the input string with all invalid characters +// for HTML code replaced by their HTML equivalents. +//////////////////////////////////////////////////////////////////// +string +escape_html(const string &input) { + static const struct { + const char *name; + int code; + } tokens[] = { + { "amp", '&' }, { "lt", '<' }, { "gt", '>' }, { "quot", '"' }, + { "nbsp", 160 }, + + { "iexcl", 161 }, { "cent", 162 }, { "pound", 163 }, { "curren", 164 }, + { "yen", 165 }, { "brvbar", 166 }, { "brkbar", 166 }, { "sect", 167 }, + { "uml", 168 }, { "die", 168 }, { "copy", 169 }, { "ordf", 170 }, + { "laquo", 171 }, { "not", 172 }, { "shy", 173 }, { "reg", 174 }, + { "macr", 175 }, { "hibar", 175 }, { "deg", 176 }, { "plusmn", 177 }, + { "sup2", 178 }, { "sup3", 179 }, { "acute", 180 }, { "micro", 181 }, + { "para", 182 }, { "middot", 183 }, { "cedil", 184 }, { "sup1", 185 }, + { "ordm", 186 }, { "raquo", 187 }, { "frac14", 188 }, { "frac12", 189 }, + { "frac34", 190 }, { "iquest", 191 }, { "Agrave", 192 }, { "Aacute", 193 }, + { "Acirc", 194 }, { "Atilde", 195 }, { "Auml", 196 }, { "Aring", 197 }, + { "AElig", 198 }, { "Ccedil", 199 }, { "Egrave", 200 }, { "Eacute", 201 }, + { "Ecirc", 202 }, { "Euml", 203 }, { "Igrave", 204 }, { "Iacute", 205 }, + { "Icirc", 206 }, { "Iuml", 207 }, { "ETH", 208 }, { "Dstrok", 208 }, + { "Ntilde", 209 }, { "Ograve", 210 }, { "Oacute", 211 }, { "Ocirc", 212 }, + { "Otilde", 213 }, { "Ouml", 214 }, { "times", 215 }, { "Oslash", 216 }, + { "Ugrave", 217 }, { "Uacute", 218 }, { "Ucirc", 219 }, { "Uuml", 220 }, + { "Yacute", 221 }, { "THORN", 222 }, { "szlig", 223 }, { "agrave", 224 }, + { "aacute", 225 }, { "acirc", 226 }, { "atilde", 227 }, { "auml", 228 }, + { "aring", 229 }, { "aelig", 230 }, { "ccedil", 231 }, { "egrave", 232 }, + { "eacute", 233 }, { "ecirc", 234 }, { "euml", 235 }, { "igrave", 236 }, + { "iacute", 237 }, { "icirc", 238 }, { "iuml", 239 }, { "eth", 240 }, + { "ntilde", 241 }, { "ograve", 242 }, { "oacute", 243 }, { "ocirc", 244 }, + { "otilde", 245 }, { "ouml", 246 }, { "divide", 247 }, { "oslash", 248 }, + { "ugrave", 249 }, { "uacute", 250 }, { "ucirc", 251 }, { "uuml", 252 }, + { "yacute", 253 }, { "thorn", 254 }, { "yuml", 255 }, + + { NULL, 0 }, + }; + + string result; + for (string::const_iterator ii = input.begin(); + ii != input.end(); + ++ii) { + int code = (unsigned char)(*ii); + bool found_match = false; + for (int i = 0; !found_match && tokens[i].name != NULL; i++) { + if (code == tokens[i].code) { + result += '&'; + result += tokens[i].name; + result += ';'; + found_match = true; + } + } + + if (!found_match) { + result += (*ii); + } + } + + return result; +} diff --git a/pandaapp/src/indexify/indexParameters.h b/pandaapp/src/indexify/indexParameters.h index 3bc8e0ab9d..74c95693df 100644 --- a/pandaapp/src/indexify/indexParameters.h +++ b/pandaapp/src/indexify/indexParameters.h @@ -83,6 +83,9 @@ extern bool force_regenerate; // True to use the Rose formatting convention for roll directory names. extern bool format_rose; +// True to sort roll directory names by date. Useful only with -r. +extern bool sort_date; + // True to place dummy thumbnails instead of loading actual images. extern bool dummy_mode; @@ -127,6 +130,11 @@ extern int actual_index_width; extern int thumb_interior_width; extern int thumb_interior_height; +Filename compose_href(const Filename &rel_dir, const Filename &user_prefix, + const Filename &basename = Filename()); + +string escape_html(const string &input); + #endif diff --git a/pandaapp/src/indexify/indexify.cxx b/pandaapp/src/indexify/indexify.cxx index 631aa0ebc0..bf862bedbf 100644 --- a/pandaapp/src/indexify/indexify.cxx +++ b/pandaapp/src/indexify/indexify.cxx @@ -25,6 +25,8 @@ #include "indexParameters.h" #include "string_utils.h" +#include + //////////////////////////////////////////////////////////////////// // Function: Indexify::Constructor // Access: Public @@ -34,6 +36,7 @@ Indexify:: Indexify() { clear_runlines(); add_runline("[opts] roll1-dir roll2-dir [roll3-dir ...]"); + add_runline("[opts] full/*"); set_program_description ("This program reads a collection of directories containing photo " @@ -47,18 +50,24 @@ Indexify() { "should be within the same parent directory. Each directory is " "considered a \"roll\", which may or may not correspond to a physical " "roll of film, and the photos within each directory are grouped " - "correspondingly on the generated HTML pages.\n\n" + "correspondingly on the generated HTML pages. One common special case " + "is in which all the roll directories are found in the subdirectory " + "named \"full\" (e.g., the second example above) or \"reduced\". This " + "keeps the root directory nice and clean.\n\n" "If a file exists by the same name as an image file but with the " "extension \"cm\", that file is taken to be a HTML comment about that " "particular image and is inserted the HTML page for that image. " "Similarly, if there is a file within a roll directory with the same " "name as the directory itself (but with the extension \"cm\"), that file " - "is inserted into the front page to introduce that particular roll.\n\n" + "is inserted into the front page to introduce that particular roll. " + "Finally, a file with the name of the directory, but with the extension " + "\"ds\" may contain a brief one-line description of the directory, for " + "the toplevel index page.\n\n" "Normally, all image files with the specified extension (normally " "\"jpg\") within a roll directory are included in the index, and sorted " - "into alphabetical (or numeric) order. If you wish to specify a " + "into alphabetical (or numerical) order. If you wish to specify a " "different order, or use only a subset of the images in a directory, " "create a file in the roll directory with the same name as the " "directory itself, and the extension \"ls\". This file should " @@ -81,8 +90,8 @@ Indexify() { add_option ("r", "relative-dir", 0, - "When -a is specifies to place the generate html files in a directory " - "other than the one above the actual roll directories, you may need " + "When -a is specified to place the generated html files in a directory " + "other than the default, you may need " "to specify how the html files will address the roll directories. This " "parameter specifies the relative path to the directory above the roll " "directories, from the directory named by -a.", @@ -103,6 +112,12 @@ Indexify() { "name will be reformatted to m-yy/s for output.", &Indexify::dispatch_none, &format_rose); + add_option + ("s", "", 0, + "When used in conjunction with -r, requests sorting of the roll " + "directory names by date.", + &Indexify::dispatch_none, &sort_date); + add_option ("d", "", 0, "Run in \"dummy\" mode; don't load any images, but instead just " @@ -261,7 +276,8 @@ handle_args(ProgramBase::Args &args) { filename.standardize(); if (filename.is_directory()) { string basename = filename.get_basename(); - if (basename == "icons" || basename == "html" || basename == "reduced") { + if (basename == "icons" || basename == "html" || + basename == "reduced" || basename == "thumbs") { nout << "Ignoring " << filename << "; indexify-generated directory.\n"; } else { @@ -287,6 +303,13 @@ handle_args(ProgramBase::Args &args) { return true; } +class SortRollDirs { +public: + bool operator () (const RollDirectory *a, const RollDirectory *b) const { + return a->sort_date_before(*b); + } +}; + //////////////////////////////////////////////////////////////////// // Function: Indexify::post_command_line // Access: Protected, Virtual @@ -307,6 +330,12 @@ post_command_line() { if (_archive_dir.empty()) { // Choose a default archive directory, above the first roll directory. _archive_dir = _roll_dirs.front()->get_dir().get_dirname(); + string parent_dirname = _archive_dir.get_basename(); + if (parent_dirname == "full" || parent_dirname == "reduced") { + // As a special case, if the subdirectory name is "full" or + // "reduced", use the directory above that. + _archive_dir = _archive_dir.get_dirname(); + } if (_archive_dir.empty()) { _archive_dir = "."; } @@ -317,12 +346,17 @@ post_command_line() { _roll_dir_root.standardize(); } + // Sort the roll directories, if specified. + if (format_rose && sort_date) { + sort(_roll_dirs.begin(), _roll_dirs.end(), SortRollDirs()); + } + if (_front_title.empty()) { // Supply a default title. if (_roll_dirs.size() == 1) { _front_title = _roll_dirs.front()->get_name(); } else { - _front_title = _roll_dirs.front()->get_name() + " to " + _roll_dirs.back()->get_name(); + _front_title = _roll_dirs.front()->get_name() + " through " + _roll_dirs.back()->get_name(); } } @@ -479,17 +513,26 @@ run() { } // Then go back and generate the HTML. + for (di = _roll_dirs.begin(); di != _roll_dirs.end(); ++di) { + RollDirectory *roll_dir = (*di); + if (!roll_dir->generate_html(_archive_dir, _roll_dir_root)) { + nout << "Failure.\n"; + exit(1); + } + } - Filename html_filename(_archive_dir, "index.htm"); - nout << "Generating " << html_filename << "\n"; - html_filename.set_text(); - ofstream root_html; - if (!html_filename.open_write(root_html)) { - nout << "Unable to write to " << html_filename << "\n"; + // Generate the complete index that browses all the roll directories + // at once. + Filename complete_filename(_archive_dir, "html/complete.htm"); + nout << "Generating " << complete_filename << "\n"; + complete_filename.set_text(); + ofstream complete_html; + if (!complete_filename.open_write(complete_html)) { + nout << "Unable to write to " << complete_filename << "\n"; exit(1); } - root_html + complete_html << "\n" << "\n" << "" << _front_title << "\n" @@ -499,13 +542,63 @@ run() { for (di = _roll_dirs.begin(); di != _roll_dirs.end(); ++di) { RollDirectory *roll_dir = (*di); - if (!roll_dir->generate_html(root_html, _archive_dir, _roll_dir_root)) { - nout << "Failure.\n"; - exit(1); + complete_html + << roll_dir->get_comment_html() + << roll_dir->get_index_html(); + } + + complete_html << "

\n"; + if (!up_icon.empty()) { + // Use an icon to go up. + Filename up_icon_href = compose_href("..", up_icon); + complete_html + << "\"return\n"; + } else { + // No up icon; use text to go up. + complete_html + << "
Return to index\n"; + } + complete_html << "

\n"; + + complete_html + << "\n" + << "\n"; + + // And finally, generate the index HTML file that sits on the top of + // all of this. + Filename index_filename(_archive_dir, "index.htm"); + nout << "Generating " << index_filename << "\n"; + index_filename.set_text(); + ofstream index_html; + if (!index_filename.open_write(index_html)) { + nout << "Unable to write to " << index_filename << "\n"; + exit(1); + } + + index_html + << "\n" + << "\n" + << "" << _front_title << "\n" + << "\n" + << "\n" + << "

" << _front_title << "

\n" + << "\n" + << "(complete archive)\n" << "\n" << "\n"; } diff --git a/pandaapp/src/indexify/rollDirectory.cxx b/pandaapp/src/indexify/rollDirectory.cxx index 3220502e03..910e004262 100644 --- a/pandaapp/src/indexify/rollDirectory.cxx +++ b/pandaapp/src/indexify/rollDirectory.cxx @@ -101,6 +101,16 @@ get_name() const { return _name; } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::get_desc +// Access: Public +// Description: Returns the one-line description for the directory. +//////////////////////////////////////////////////////////////////// +const string &RollDirectory:: +get_desc() const { + return _desc; +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::scan // Access: Public @@ -111,9 +121,47 @@ scan(const string &extension) { bool reverse_order = false; bool explicit_list = false; + // Check for a .ds file, which contains a one-line description of + // the contents of the directory. + Filename ds_filename(_basename); + ds_filename.set_extension("ds"); + if (cm_search.is_empty() || !ds_filename.resolve_filename(cm_search)) { + // If the ds file isn't found along the search path specified + // via -cmdir on the command line, then look for it in the + // appropriate source directory. + ds_filename = Filename(_dir, ds_filename); + } + if (ds_filename.exists()) { + ds_filename.set_text(); + ifstream ds; + if (!ds_filename.open_read(ds)) { + nout << "Could not read " << ds_filename << "\n"; + } else { + // Get the words out one at a time and put just one space + // between them. + string word; + ds >> word; + while (!ds.eof() && !ds.fail()) { + if (!_desc.empty()) { + _desc += ' '; + } + _desc += word; + word = string(); + ds >> word; + } + if (!word.empty()) { + if (!_desc.empty()) { + _desc += ' '; + } + _desc += word; + } + } + } + // Check for an .ls file in the roll directory, which may give an // explicit ordering, or if empty, it specifies reverse ordering. - Filename ls_filename(_dir, _basename + ".ls"); + Filename ls_filename(_dir, _basename); + ls_filename.set_extension("ls"); if (ls_filename.exists()) { add_contributing_filename(ls_filename); ls_filename.set_text(); @@ -213,6 +261,51 @@ collect_index_images() { } } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::sort_date_before +// Access: Public +// Description: Returns true if the given directory name should sort +// before the other one, assuming the Rose naming +// convention of mmyyss is in place. +//////////////////////////////////////////////////////////////////// +bool RollDirectory:: +sort_date_before(const RollDirectory &other) const { + if (_name == _basename && other._name == other._basename) { + // If Rose naming convention is not in place in either case, sort + // alphabetically. + return _basename < other._basename; + + } else if (_name == _basename) { + // If Rose naming convention is in place on this one and not the + // other, it sorts first. + return true; + + } else if (other._name == other._basename) { + // And vice-versa. + return false; + + } else { + // Rose naming convention holds. Sort based on year first. Years + // above 90 are deemed to belong to the previous century. + string yy = _basename.substr(2, 2); + string other_yy = other._basename.substr(2, 2); + int year = atoi(yy.c_str()); + int other_year = atoi(other_yy.c_str()); + if (year < 90) { + year += 100; + } + if (other_year < 90) { + other_year += 100; + } + if (year != other_year) { + return year < other_year; + } + + // After year, sort alphabetically. + return _basename < other._basename; + } +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::get_newest_contributing_filename // Access: Public @@ -310,17 +403,18 @@ generate_images(const Filename &archive_dir, PNMTextMaker *text_maker) { // Access: Public // Description: Generates all appropriate HTML files for this // directory, and generate the appropriate HTML code -// into the root_html file. +// into the html strings (retrieved by +// get_comment_html() and get_index_html()). //////////////////////////////////////////////////////////////////// bool RollDirectory:: -generate_html(ostream &root_html, const Filename &archive_dir, - const Filename &roll_dir_root) { +generate_html(const Filename &archive_dir, const Filename &roll_dir_root) { if (is_empty()) { return true; } nassertr(!_index_images.empty(), false); - root_html + ostringstream comment_strm; + comment_strm << "\n"; if (!omit_roll_headers) { @@ -336,32 +430,122 @@ generate_html(ostream &root_html, const Filename &archive_dir, if (cm_filename.exists()) { // If the comment file for the roll exists, insert its contents // here instead of the generic header. - if (!insert_html_comment(root_html, cm_filename)) { + if (!insert_html_comment(comment_strm, cm_filename)) { return false; } } else { - root_html + comment_strm << "

" << _name << "

\n"; + if (!_desc.empty()) { + comment_strm << "

" << escape_html(_desc) << ".

\n"; + } } } + _comment_html = comment_strm.str(); - nout << "Generating " << Filename(archive_dir, "html/") - << _basename << "/*\n"; + Filename html_dir(archive_dir, "html"); + nout << "Generating " << Filename(html_dir, _basename) << "/*\n"; - root_html << "

\n"; + ostringstream index_strm; + index_strm << "

\n"; IndexImages::iterator ii; for (ii = _index_images.begin(); ii != _index_images.end(); ++ii) { IndexImage *index_image = (*ii); - if (!index_image->generate_html(root_html, archive_dir, roll_dir_root)) { + if (!index_image->generate_html(index_strm, archive_dir, roll_dir_root)) { return false; } } - root_html << "

\n"; + index_strm << "

\n"; + _index_html = index_strm.str(); + // Also generate the index html for this directory. + Filename html_filename(html_dir, _basename); + html_filename.set_extension("htm"); + nout << "Generating " << html_filename << "\n"; + html_filename.set_text(); + ofstream index_html; + if (!html_filename.open_write(index_html)) { + nout << "Unable to write to " << html_filename << "\n"; + exit(1); + } + + string up_href = "../index.htm#" + _basename; + + Filename prev_roll_filename; + Filename next_roll_filename; + + if (_prev != (RollDirectory *)NULL) { + prev_roll_filename = _prev->_basename; + prev_roll_filename.set_extension("htm"); + } + if (_next != (RollDirectory *)NULL) { + next_roll_filename = _next->_basename; + next_roll_filename.set_extension("htm"); + } + + index_html + << "\n" + << "\n"; + if (_desc.empty()) { + index_html + << "" << _name << "\n"; + } else { + index_html + << "" << _name << " " << escape_html(_desc) << "\n"; + } + index_html + << "\n" + << "\n" + << get_comment_html(); + + generate_nav_buttons(index_html, prev_roll_filename, next_roll_filename, + up_href); + index_html << get_index_html(); + generate_nav_buttons(index_html, prev_roll_filename, next_roll_filename, + up_href); + + index_html + << "
(complete archive)\n" + << "\n" + << "\n"; + + return true; } +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::get_comment_html +// Access: Public +// Description: Returns the HTML text that describes this directory's +// index. This is set when generate_html() returns +// true. +// +// This text may be inserted into the middle of a HTML +// page to include the imagemap that references each of +// the images in this directory. +//////////////////////////////////////////////////////////////////// +const string &RollDirectory:: +get_comment_html() const { + return _comment_html; +} + +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::get_index_html +// Access: Public +// Description: Returns the HTML text that describes this directory's +// index. This is set when generate_html() returns +// true. +// +// This text may be inserted into the middle of a HTML +// page to include the imagemap that references each of +// the images in this directory. +//////////////////////////////////////////////////////////////////// +const string &RollDirectory:: +get_index_html() const { + return _index_html; +} + //////////////////////////////////////////////////////////////////// // Function: RollDirectory::output // Access: Public @@ -492,23 +676,103 @@ format_basename(const string &basename) { return basename; } - // The first four characters must be digits. - for (size_t i = 0; i < 4; i++) { - if (!isdigit(basename[i])) { - return basename; - } + // The first two characters must be alphanumeric. + if (!isalnum(basename[0]) || !isalnum(basename[1])) { + return basename; } + // The next two characters must be digits. + if (!isdigit(basename[2]) || !isdigit(basename[3])) { + return basename; + } + + // If the first two were digits as well as being alphanumeric, then + // we have mm-yy/sequence. Otherwise, we just have xxyy/sequence. + bool mm_is_month = (isdigit(basename[0]) && isdigit(basename[1])); + string mm = basename.substr(0, 2); string yy = basename.substr(2, 2); string ss = basename.substr(4); - if (mm[0] == '0') { + if (mm_is_month && mm[0] == '0') { mm = mm[1]; } while (ss.length() > 1 && ss[0] == '0') { ss = ss.substr(1); } - return mm + "-" + yy + "/" + ss; + if (mm_is_month) { + return mm + "-" + yy + "/" + ss; + } else { + return mm + yy + "/" + ss; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: RollDirectory::generate_nav_buttons +// Access: Private, Static +// Description: Outputs the HTML code to generate the next, prev, +// up buttons when viewing each reduced image. +//////////////////////////////////////////////////////////////////// +void RollDirectory:: +generate_nav_buttons(ostream &html, const Filename &prev_roll_filename, + const Filename &next_roll_filename, + const string &up_href) { + html << "

\n"; + + bool first_icons = false; + if (!prev_icon.empty() && !next_icon.empty()) { + first_icons = true; + // Use icons to go forward and back. + Filename prev_icon_href = compose_href("..", prev_icon); + if (prev_roll_filename.empty()) { + html << "\"No\n"; + } else { + html << "\"previous\"\n"; + } + + Filename next_icon_href = compose_href("..", next_icon); + if (next_roll_filename.empty()) { + html << "\"No\n"; + } else { + html << "\"next\"\n"; + } + + } else { + // No prev/next icons; use text to go forward and back. + if (prev_roll_filename.empty()) { + html << "(This is the first roll.)\n"; + } else { + html << "Back to previous roll\n"; + } + + if (next_roll_filename.empty()) { + html << "
(This is the last roll.)\n"; + } else { + html << "
On to next roll\n"; + } + } + + if (!up_href.empty()) { + if (!up_icon.empty()) { + // Use an icon to go up. + if (!first_icons) { + html << "
"; + } else { + html << "   "; + } + Filename up_icon_href = compose_href("..", up_icon); + html << "\"return\n"; + } else { + // No up icon; use text to go up. + html << "
Return to index\n"; + } + } + html << "

\n"; } diff --git a/pandaapp/src/indexify/rollDirectory.h b/pandaapp/src/indexify/rollDirectory.h index 9cb589a59b..55a78f63bc 100644 --- a/pandaapp/src/indexify/rollDirectory.h +++ b/pandaapp/src/indexify/rollDirectory.h @@ -41,9 +41,12 @@ public: const Filename &get_dir() const; const string &get_basename() const; const string &get_name() const; + const string &get_desc() const; bool scan(const string &extension); void collect_index_images(); + bool sort_date_before(const RollDirectory &other) const; + const Filename &get_newest_contributing_filename() const; bool is_empty() const; @@ -54,8 +57,10 @@ public: IndexImage *get_index_image(int n) const; bool generate_images(const Filename &archive_dir, PNMTextMaker *text_maker); - bool generate_html(ostream &root_html, const Filename &archive_dir, - const Filename &roll_dir_root); + bool generate_html(const Filename &archive_dir, + const Filename &roll_dir_root); + const string &get_comment_html() const; + const string &get_index_html() const; void output(ostream &out) const; void write(ostream &out, int indent_level) const; @@ -66,6 +71,10 @@ private: void add_contributing_filename(const Filename &filename); static bool insert_html_comment_body(ostream &html, istream &cm); static string format_basename(const string &basename); + void generate_nav_buttons(ostream &html, + const Filename &prev_roll_filename, + const Filename &next_roll_filename, + const string &up_href); public: RollDirectory *_prev; @@ -75,6 +84,7 @@ private: Filename _dir; string _basename; string _name; + string _desc; typedef pvector Photos; Photos _photos; @@ -82,6 +92,9 @@ private: typedef pvector IndexImages; IndexImages _index_images; + + string _comment_html; + string _index_html; }; INLINE ostream &operator << (ostream &out, const RollDirectory &d) {