diff options
| author | Matthias Melcher <github@matthiasm.com> | 2025-07-05 15:25:11 +0200 |
|---|---|---|
| committer | Matthias Melcher <github@matthiasm.com> | 2025-07-05 15:25:11 +0200 |
| commit | 5d9961c1c356ad971b5dc084ab957bc82926d8c0 (patch) | |
| tree | 767c40d17468de95ef213932b8d6523f5508e718 /src | |
| parent | d3c6135c08b3240a478968f59435ce62bdfa87e5 (diff) | |
Fl_Help_View: Finl formatting and documentation
- restructure header file
- sort source file reflecting header
- documentation of text selection
Diffstat (limited to 'src')
| -rw-r--r-- | src/Fl_Help_View.cxx | 2477 |
1 files changed, 1264 insertions, 1213 deletions
diff --git a/src/Fl_Help_View.cxx b/src/Fl_Help_View.cxx index 283e92f09..8bd3a8aa6 100644 --- a/src/Fl_Help_View.cxx +++ b/src/Fl_Help_View.cxx @@ -64,10 +64,19 @@ static constexpr int MAX_COLUMNS = 200; // -// global flag for image loading (see get_image). +// global and class static values // static char initial_load = 0; +// We don't put the offscreen buffer in the help view class because +// we'd need to include platform.H in the header... +static Fl_Offscreen fl_help_view_buffer; +int Fl_Help_View::selection_push_first_ = 0; +int Fl_Help_View::selection_push_last_ = 0; +int Fl_Help_View::selection_drag_first_ = 0; +int Fl_Help_View::selection_drag_last_ = 0; +Fl_Help_View::Mode Fl_Help_View::draw_mode_ = Mode::DRAW; +int Fl_Help_View::current_pos_ = 0; // // Local functions declarations, implementations are at the end of the file @@ -76,6 +85,13 @@ static char initial_load = 0; static int quote_char(const char *); static std::string to_lower(const std::string &str); static size_t url_scheme(const std::string &url, bool skip_slashes=false); +static const char *vanilla(const char *p, const char *end); +static uint32_t command(const char *cmd); + +static constexpr uint32_t CMD(char a, char b, char c, char d) +{ + return ((a<<24)|(b<<16)|(c<<8)|d); +} // // Static data. @@ -306,95 +322,168 @@ size_t Fl_Help_View::Font_Stack::count() const { } -// ---- Implementation of text selection in Fl_Help_View +// ------ Fl_Help_View Private methods -/* TODO: matt: - The selection code was implemented with binary compatibility within 1.4 - in mind. This means that I was limited to adding static variables - only to not enlarge the Fl_Help_View class. +// ---- HTML source and raw data, getter - The implementation of selection should be change to use class local variables - so text in multiple Fl_Help_View widgets can be selected independently. +/** + \brief Frees memory used for the document. + */ +void Fl_Help_View::free_data() { + // Release all images... + if (value_) { + const char *ptr, // Pointer into block + *attrs; // Pointer to start of element attributes + HV_Edit_Buffer buf; // Text buffer + char attr[1024], // Attribute buffer + wattr[1024], // Width attribute buffer + hattr[1024]; // Height attribute buffer - Still to do: - - &word; style characters mess up our count inside a word boundary - - we can only select words, no individual characters - - no dragging of the selection into another widget - - selection must be cleared if another widget get focus! - - write a comment for every new function - */ + DEBUG_FUNCTION(__LINE__,__FUNCTION__); -/* - The following functions are also used to draw stuff and should be replaced with - local copies that are much faster when merely counting: - - fl_color(Fl_Color); - fl_rectf(int, int, int, int); - fl_push_clip(int, int, int, int); - fl_xyline(int, int, int); - fl_rect() - fl_line() - img->draw() -*/ + for (ptr = value_; *ptr;) + { + if (*ptr == '<') + { + ptr ++; -// We don't put the offscreen buffer in the help view class because -// we'd need to include platform.H in the header... -static Fl_Offscreen fl_help_view_buffer; -int Fl_Help_View::selection_push_first_ = 0; -int Fl_Help_View::selection_push_last_ = 0; -int Fl_Help_View::selection_drag_first_ = 0; -int Fl_Help_View::selection_drag_last_ = 0; -Fl_Help_View::Mode Fl_Help_View::draw_mode_ = Mode::DRAW; -int Fl_Help_View::current_pos_ = 0; + if (strncmp(ptr, "!--", 3) == 0) + { + // Comment... + ptr += 3; + if ((ptr = strstr(ptr, "-->")) != nullptr) + { + ptr += 3; + continue; + } + else + break; + } + + buf.clear(); + + while (*ptr && *ptr != '>' && !isspace((*ptr)&255)) + buf += *ptr++; + + attrs = ptr; + while (*ptr && *ptr != '>') + ptr ++; + + if (*ptr == '>') + ptr ++; + + if (buf.cmp("IMG")) + { + Fl_Shared_Image *img; + int width; + int height; + + get_attr(attrs, "WIDTH", wattr, sizeof(wattr)); + get_attr(attrs, "HEIGHT", hattr, sizeof(hattr)); + width = get_length(wattr); + height = get_length(hattr); + + if (get_attr(attrs, "SRC", attr, sizeof(attr))) { + // Get and release the image to free it from memory... + img = get_image(attr, width, height); + if ((void*)img != &broken_image) { + img->release(); + } + } + } + } + else + ptr++; + } + + free((void *)value_); + value_ = 0; + } + + blocks_ .clear(); + link_list_.clear(); + target_line_map_.clear(); +} /** - \brief Draws a text string in the help view. + \brief Find out if the mouse is over a hyperlink and return the link data. + \parm[in] xx, yy Pixel coordinates inside the widget. + \return Shared pointer to the link if found, nullptr otherwise. + */ +std::shared_ptr<Fl_Help_View::Link> Fl_Help_View::find_link(int xx, int yy) +{ + for (auto &link : link_list_) { + if (link->box.contains(xx, yy)) { + return link; + } + } + return nullptr; +} - This function draws the text string \p t at position (\p x, \p y) in the help view. - If the text is selected, it draws a selection rectangle around it and changes the text color. - \param[in] t Text to draw - \param[in] x X position to draw at - \param[in] y Y position to draw at - \param[in] entity_extra_length (unclear) +/** + \brief Follow a link and load the target document or scroll to the target. + This function clears the current selection and loads a new document or + scrolls to a target line in the current document. + \param linkp Shared pointer to the link to follow. */ -void Fl_Help_View::hv_draw(const char *t, int x, int y, int entity_extra_length) +void Fl_Help_View::follow_link(std::shared_ptr<Link> linkp) { - if (draw_mode_ == Mode::DRAW) { - if (selected_ && current_pos_<selection_last_ && current_pos_>=selection_first_) { - Fl_Color c = fl_color(); - fl_color(tmp_selection_color_); - int w = (int)fl_width(t); - if (current_pos_+(int)strlen(t)<selection_last_) - w += (int)fl_width(' '); - fl_rectf(x, y+fl_descent()-fl_height(), w, fl_height()); - fl_color(selection_text_color_); - fl_draw(t, x, y); - fl_color(c); + clear_selection(); + set_changed(); + std::string target = linkp->target;; // Current target + + if ( (linkp->filename_ != filename_) && !linkp->filename_.empty() ) { + // Load the new document, if the filename is different + std::string url; + size_t directory_scheme_length = url_scheme(directory_); + size_t filename_scheme_length = url_scheme(linkp->filename_); + if ( (directory_scheme_length > 0) && (filename_scheme_length == 0) ) { + // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not: + if (linkp->filename_[0] == '/') { + // If linkp->filename_ is absolute... + url = directory_.substr(0, directory_scheme_length) + linkp->filename_;; + } else { + // If linkp->filename_ is relative, the URL is the directory_ plus the filename + url = directory_ + "/" + linkp->filename_; + } + } else if (linkp->filename_[0] != '/' && (filename_scheme_length == 0)) { + // If the filename is relative and does not start with a scheme (ftp: , etc.)... + if (!directory_.empty()) { + // If we have a current directory, use that as the base for the URL + url = directory_ + "/" + linkp->filename_; + } else { + // If we do not have a current directory, use the application's current working directory + char dir[FL_PATH_MAX]; // Current directory (static size ok until we have fl_getcwd_std() + fl_getcwd(dir, sizeof(dir)); + url = "file:" + std::string(dir) + "/" + linkp->filename_; + } } else { - fl_draw(t, x, y); + // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is + url = linkp->filename_; } - } else { - // If draw_mode_ is not DRAW, we don't actually draw anything, but instead - // measure where text blocks are on screen during a mouse selection process. - int w = (int)fl_width(t); - if ( (Fl::event_x() >= x) && (Fl::event_x() < x+w) ) { - if ( (Fl::event_y() >= y-fl_height()+fl_descent()) && (Fl::event_y() <= y+fl_descent()) ) { - int f = (int) current_pos_; - int l = (int) (f+strlen(t)); // use 'quote_char' to calculate the true length of the HTML string - if (draw_mode_ == Mode::PUSH) { - selection_push_first_ = f; - selection_push_last_ = l; - } else { // Mode::DRAG - selection_drag_first_ = f; - selection_drag_last_ = l + entity_extra_length; - } - } + + // If a target is specified, append it to the URL + if (!linkp->target.empty()) { + url += "#" + linkp->target; } + + load(url.c_str()); + + } else if (!target.empty()) { + // Keep the same document, scroll to the target line + topline(target.c_str()); + } else { + // No target, no filename, just scroll to the top of the document + topline(0); } + + // Scroll the content horizontally to the left + leftline(0); } +// ---- HTML interpretation and formatting /** \brief Adds a text block to the list. @@ -517,705 +606,6 @@ int Fl_Help_View::do_align( /** - \brief Draws the Fl_Help_View widget. -*/ -void Fl_Help_View::draw() -{ - int i; // Looping var - const Text_Block *block; // Pointer to current block - const char *ptr, // Pointer to text in block - *attrs; // Pointer to start of element attributes - HV_Edit_Buffer buf; // Text buffer - char attr[1024]; // Attribute buffer - int xx, yy, ww, hh; // Current positions and sizes - int line; // Current line - Fl_Font font; - Fl_Fontsize fsize; // Current font and size - Fl_Color fcolor; // current font color - int head, pre, // Flags for text - needspace; // Do we need whitespace? - Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; - // Box to draw... - int underline, // Underline text? - xtra_ww; // Extra width for underlined space between words - - DEBUG_FUNCTION(__LINE__,__FUNCTION__); - - // Draw the scrollbar(s) and box first... - ww = w(); - hh = h(); - i = 0; - - draw_box(b, x(), y(), ww, hh, bgcolor_); - - if ( hscrollbar_.visible() || scrollbar_.visible() ) { - int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); - int hor_vis = hscrollbar_.visible(); - int ver_vis = scrollbar_.visible(); - // Scrollbar corner - int scorn_x = x() + ww - (ver_vis?scrollsize:0) - Fl::box_dw(b) + Fl::box_dx(b); - int scorn_y = y() + hh - (hor_vis?scrollsize:0) - Fl::box_dh(b) + Fl::box_dy(b); - if ( hor_vis ) { - if ( hscrollbar_.h() != scrollsize ) { // scrollsize changed? - hscrollbar_.resize(x(), scorn_y, scorn_x - x(), scrollsize); - init_sizes(); - } - draw_child(hscrollbar_); - hh -= scrollsize; - } - if ( ver_vis ) { - if ( scrollbar_.w() != scrollsize ) { // scrollsize changed? - scrollbar_.resize(scorn_x, y(), scrollsize, scorn_y - y()); - init_sizes(); - } - draw_child(scrollbar_); - ww -= scrollsize; - } - if ( hor_vis && ver_vis ) { - // Both scrollbars visible? Draw little gray box in corner - fl_color(FL_GRAY); - fl_rectf(scorn_x, scorn_y, scrollsize, scrollsize); - } - } - - if (!value_) - return; - - if (selected_) { - if (Fl::focus() == this) { - // If this widget has the focus, we use the selection color directly - tmp_selection_color_ = selection_color(); - } else { - // Otherwise we blend the selection color with the background color - tmp_selection_color_ = fl_color_average(bgcolor_, selection_color(), 0.8f); - } - selection_text_color_ = fl_contrast(textcolor_, tmp_selection_color_); - } - current_pos_ = 0; - - // Clip the drawing to the inside of the box... - fl_push_clip(x() + Fl::box_dx(b), y() + Fl::box_dy(b), - ww - Fl::box_dw(b), hh - Fl::box_dh(b)); - fl_color(textcolor_); - - // Draw all visible blocks... - for (i = 0, block = &blocks_[0]; i < (int)blocks_.size(); i ++, block ++) - if ((block->y + block->h) >= topline_ && block->y < (topline_ + h())) - { - line = 0; - xx = block->line[line]; - yy = block->y - topline_; - hh = 0; - pre = 0; - head = 0; - needspace = 0; - underline = 0; - - initfont(font, fsize, fcolor); - // byte length difference between html entity (encoded by &...;) and - // UTF-8 encoding of same character - int entity_extra_length = 0; - for (ptr = block->start, buf.clear(); ptr < block->end;) - { - if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0) - { - if (!head && !pre) - { - // Check width... - ww = buf.width(); - - if (needspace && xx > block->x) - xx += (int)fl_width(' '); - - if ((xx + ww) > block->w) - { - if (line < 31) - line ++; - xx = block->line[line]; - yy += hh; - hh = 0; - } - - hv_draw(buf.c_str(), xx + x() - leftline_, yy + y(), entity_extra_length); - buf.clear(); - entity_extra_length = 0; - if (underline) { - xtra_ww = isspace((*ptr)&255)?(int)fl_width(' '):0; - fl_xyline(xx + x() - leftline_, yy + y() + 1, - xx + x() - leftline_ + ww + xtra_ww); - } - current_pos_ = (int) (ptr-value_); - - xx += ww; - if ((fsize + 2) > hh) - hh = fsize + 2; - - needspace = 0; - } - else if (pre) - { - while (isspace((*ptr)&255)) - { - if (*ptr == '\n') - { - hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); - if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, - xx + x() - leftline_ + buf.width()); - buf.clear(); - current_pos_ = (int) (ptr-value_); - if (line < 31) - line ++; - xx = block->line[line]; - yy += hh; - hh = fsize + 2; - } - else if (*ptr == '\t') - { - // Do tabs every 8 columns... - buf += ' '; // add at least one space - while (buf.size() & 7) - buf += ' '; - } - else { - buf += ' '; - } - if ((fsize + 2) > hh) - hh = fsize + 2; - - ptr ++; - } - - if (buf.size() > 0) - { - hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); - ww = buf.width(); - buf.clear(); - if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, - xx + x() - leftline_ + ww); - xx += ww; - current_pos_ = (int) (ptr-value_); - } - - needspace = 0; - } - else - { - buf.clear(); - - while (isspace((*ptr)&255)) - ptr ++; - current_pos_ = (int) (ptr-value_); - } - } - - if (*ptr == '<') - { - ptr ++; - - if (strncmp(ptr, "!--", 3) == 0) - { - // Comment... - ptr += 3; - if ((ptr = strstr(ptr, "-->")) != nullptr) - { - ptr += 3; - continue; - } - else - break; - } - - while (*ptr && *ptr != '>' && !isspace((*ptr)&255)) - buf += *ptr++; - - attrs = ptr; - while (*ptr && *ptr != '>') - ptr ++; - - if (*ptr == '>') - ptr ++; - - // end of command reached, set the supposed start of printed eord here - current_pos_ = (int) (ptr-value_); - if (buf.cmp("HEAD")) - head = 1; - else if (buf.cmp("BR")) - { - if (line < 31) - line ++; - xx = block->line[line]; - yy += hh; - hh = 0; - } - else if (buf.cmp("HR")) - { - fl_line(block->x + x(), yy + y(), block->w + x(), - yy + y()); - - if (line < 31) - line ++; - xx = block->line[line]; - yy += 2 * fsize;//hh; - hh = 0; - } - else if (buf.cmp("CENTER") || - buf.cmp("P") || - buf.cmp("H1") || - buf.cmp("H2") || - buf.cmp("H3") || - buf.cmp("H4") || - buf.cmp("H5") || - buf.cmp("H6") || - buf.cmp("UL") || - buf.cmp("OL") || - buf.cmp("DL") || - buf.cmp("LI") || - buf.cmp("DD") || - buf.cmp("DT") || - buf.cmp("PRE")) - { - if (tolower(buf[0]) == 'h') - { - font = FL_HELVETICA_BOLD; - fsize = textsize_ + '7' - buf[1]; - } - else if (buf.cmp("DT")) - { - font = textfont_ | FL_ITALIC; - fsize = textsize_; - } - else if (buf.cmp("PRE")) - { - font = FL_COURIER; - fsize = textsize_; - pre = 1; - } - - if (buf.cmp("LI")) - { - if (block->ol) { - char buf[10]; - snprintf(buf, sizeof(buf), "%d. ", block->ol_num); - hv_draw(buf, xx - (int)fl_width(buf) + x() - leftline_, yy + y()); - } - else { - // draw bullet (•) Unicode: U+2022, UTF-8 (hex): e2 80 a2 - unsigned char bullet[4] = { 0xe2, 0x80, 0xa2, 0x00 }; - hv_draw((char *)bullet, xx - fsize + x() - leftline_, yy + y()); - } - } - - pushfont(font, fsize); - buf.clear(); - } - else if (buf.cmp("A") && - get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr) - { - fl_color(linkcolor_); - underline = 1; - } - else if (buf.cmp("/A")) - { - fl_color(textcolor_); - underline = 0; - } - else if (buf.cmp("FONT")) - { - if (get_attr(attrs, "COLOR", attr, sizeof(attr)) != nullptr) { - textcolor_ = get_color(attr, textcolor_); - } - - if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) { - if (!strncasecmp(attr, "helvetica", 9) || - !strncasecmp(attr, "arial", 5) || - !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA; - else if (!strncasecmp(attr, "times", 5) || - !strncasecmp(attr, "serif", 5)) font = FL_TIMES; - else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL; - else font = FL_COURIER; - } - - if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) { - if (isdigit(attr[0] & 255)) { - // Absolute size - fsize = (int)(textsize_ * pow(1.2, atof(attr) - 3.0)); - } else { - // Relative size - fsize = (int)(fsize * pow(1.2, atof(attr) - 3.0)); - } - } - - pushfont(font, fsize); - } - else if (buf.cmp("/FONT")) - { - popfont(font, fsize, textcolor_); - } - else if (buf.cmp("U")) - underline = 1; - else if (buf.cmp("/U")) - underline = 0; - else if (buf.cmp("B") || - buf.cmp("STRONG")) - pushfont(font |= FL_BOLD, fsize); - else if (buf.cmp("TD") || - buf.cmp("TH")) - { - int tx, ty, tw, th; - - if (tolower(buf[1]) == 'h') - pushfont(font |= FL_BOLD, fsize); - else - pushfont(font = textfont_, fsize); - - tx = block->x - 4 - leftline_; - ty = block->y - topline_ - fsize - 3; - tw = block->w - block->x + 7; - th = block->h + fsize - 5; - - if (tx < 0) - { - tw += tx; - tx = 0; - } - - if (ty < 0) - { - th += ty; - ty = 0; - } - - tx += x(); - ty += y(); - - if (block->bgcolor != bgcolor_) - { - fl_color(block->bgcolor); - fl_rectf(tx, ty, tw, th); - fl_color(textcolor_); - } - - if (block->border) - fl_rect(tx, ty, tw, th); - } - else if (buf.cmp("I") || - buf.cmp("EM")) - pushfont(font |= FL_ITALIC, fsize); - else if (buf.cmp("CODE") || - buf.cmp("TT")) - pushfont(font = FL_COURIER, fsize); - else if (buf.cmp("KBD")) - pushfont(font = FL_COURIER_BOLD, fsize); - else if (buf.cmp("VAR")) - pushfont(font = FL_COURIER_ITALIC, fsize); - else if (buf.cmp("/HEAD")) - head = 0; - else if (buf.cmp("/H1") || - buf.cmp("/H2") || - buf.cmp("/H3") || - buf.cmp("/H4") || - buf.cmp("/H5") || - buf.cmp("/H6") || - buf.cmp("/B") || - buf.cmp("/STRONG") || - buf.cmp("/I") || - buf.cmp("/EM") || - buf.cmp("/CODE") || - buf.cmp("/TT") || - buf.cmp("/KBD") || - buf.cmp("/VAR")) - popfont(font, fsize, fcolor); - else if (buf.cmp("/PRE")) - { - popfont(font, fsize, fcolor); - pre = 0; - } - else if (buf.cmp("IMG")) - { - Fl_Shared_Image *img = 0; - int width, height; - char wattr[8], hattr[8]; - - - get_attr(attrs, "WIDTH", wattr, sizeof(wattr)); - get_attr(attrs, "HEIGHT", hattr, sizeof(hattr)); - width = get_length(wattr); - height = get_length(hattr); - - if (get_attr(attrs, "SRC", attr, sizeof(attr))) { - img = get_image(attr, width, height); - if (!width) width = img->w(); - if (!height) height = img->h(); - } - - if (!width || !height) { - if (get_attr(attrs, "ALT", attr, sizeof(attr)) == nullptr) { - strcpy(attr, "IMG"); - } - } - - ww = width; - - if (needspace && xx > block->x) - xx += (int)fl_width(' '); - - if ((xx + ww) > block->w) - { - if (line < 31) - line ++; - - xx = block->line[line]; - yy += hh; - hh = 0; - } - - if (img) { - img->draw(xx + x() - leftline_, - yy + y() - fl_height() + fl_descent() + 2); - } - - xx += ww; - if ((height + 2) > hh) - hh = height + 2; - - needspace = 0; - } - buf.clear(); - } - else if (*ptr == '\n' && pre) - { - hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); - buf.clear(); - - if (line < 31) - line ++; - xx = block->line[line]; - yy += hh; - hh = fsize + 2; - needspace = 0; - - ptr ++; - current_pos_ = (int) (ptr-value_); - } - else if (isspace((*ptr)&255)) - { - if (pre) - { - if (*ptr == ' ') - buf += ' '; - else - { - // Do tabs every 8 columns... - buf += ' '; // at least one space - while (buf.size() & 7) - buf += ' '; - } - } - - ptr ++; - if (!pre) current_pos_ = (int) (ptr-value_); - needspace = 1; - } - else if (*ptr == '&') // process html entity - { - ptr ++; - - int qch = quote_char(ptr); - - if (qch < 0) - buf += '&'; - else { - size_t utf8l = buf.size(); - buf.add(qch); - utf8l = buf.size() - utf8l; // length of added UTF-8 text - const char *oldptr = ptr; - ptr = strchr(ptr, ';') + 1; - entity_extra_length += int(ptr - (oldptr-1)) - utf8l; // extra length between html entity and UTF-8 - } - - if ((fsize + 2) > hh) - hh = fsize + 2; - } - else - { - buf += *ptr++; - - if ((fsize + 2) > hh) - hh = fsize + 2; - } - } - - if (buf.size() > 0 && !pre && !head) - { - ww = buf.width(); - - if (needspace && xx > block->x) - xx += (int)fl_width(' '); - - if ((xx + ww) > block->w) - { - if (line < 31) - line ++; - xx = block->line[line]; - yy += hh; - hh = 0; - } - } - - if (buf.size() > 0 && !head) - { - hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); - if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, - xx + x() - leftline_ + ww); - current_pos_ = (int) (ptr-value_); - } - } - - fl_pop_clip(); -} // draw() - - -/** - \brief Skips over HTML tags in a text. - - In an html style text, set the character pointer p, skipping anything from a - leading '<' up to and including the closing '>'. If the end of the buffer is - reached, the function returns `end`. - - No need to handle UTF-8 here. - - \param[in] p pointer to html text, UTF-8 characters possible - \param[in] end pointer to the end of the text (need nut be NUL) - \return new pointer to text after skipping over '<...>' blocks, or `end` - if NUL was found or a '<...>' block was not closed. -*/ -static const char *vanilla(const char *p, const char *end) { - if (*p == '\0' || p >= end) return end; - for (;;) { - if (*p != '<') { - return p; - } else { - while (*p && p < end && *p != '>') p++; - } - p++; - if (*p == '\0' || p >= end) return end; - } -} - - -/** - \brief Finds the specified string \p s at starting position \p p. - - The argument \p p and the return value are offsets in Fl_Help_View::value(), - counting from 0. If \p p is out of range, 0 is used. - - The string comparison is simple but honors some special cases: - - the specified string \p s must be in UTF-8 encoding - - HTML tags in value() are filtered (not compared as such, they never match) - - HTML entities like '\<' or '\&x#20ac;' are converted to Unicode (UTF-8) - - ASCII characters (7-bit, \< 0x80) are compared case insensitive - - every newline (LF, '\\n') in value() is treated like a single space - - all other strings are compared as-is (byte by byte) - - \param[in] s search string in UTF-8 encoding - \param[in] p starting position for search (0,...), Default = 0 - \return the matching position or -1 if not found -*/ -int Fl_Help_View::find(const char *s, int p) -{ - int i, // Looping var - c; // Current character - Text_Block *b; // Current block - const char *bp, // Block matching pointer - *bs, // Start of current comparison - *sp; // Search string pointer - - DEBUG_FUNCTION(__LINE__,__FUNCTION__); - - // Range check input and value... - if (!s || !value_) return -1; - - if (p < 0 || p >= (int)strlen(value_)) p = 0; - - // Look for the string... - for (i = (int)blocks_.size(), b = &blocks_[0]; i > 0; i--, b++) { - if (b->end < (value_ + p)) - continue; - - if (b->start < (value_ + p)) - bp = value_ + p; - else - bp = b->start; - - bp = vanilla(bp, b->end); - if (bp == b->end) - continue; - - for (sp = s, bs = bp; *sp && *bp && bp < b->end; ) { - bool is_html_entity = false; - if (*bp == '&') { - // decode HTML entity... - if ((c = quote_char(bp + 1)) < 0) { - c = '&'; - } else { - const char *entity_end = strchr(bp + 1, ';'); - if (entity_end) { - is_html_entity = true; // c contains the unicode character - bp = entity_end; - } else { - c = '&'; - } - } - } else { - c = *bp; - } - - if (c == '\n') c = ' '; // treat newline as a single space - - // *FIXME* *UTF-8* (A.S. 02/14/2016) - // At this point c may be an arbitrary Unicode Code Point corresponding - // to a quoted character (see above), i.e. it _can_ be a multi byte - // UTF-8 sequence and must be compared with the corresponding - // multi byte string in (*sp)... - // For instance: "€" == 0x20ac -> 0xe2 0x82 0xac (UTF-8: 3 bytes). - // Hint: use fl_utf8encode() [see below] - - int utf_len = 1; - if (c > 0x20 && c < 0x80 && tolower(*sp) == tolower(c)) { - // Check for ASCII case insensitive match. - //printf("%ld text match %c/%c\n", bp-value_, *sp, c); - sp++; - bp = vanilla(bp+1, b->end); - } else if (is_html_entity && fl_utf8decode(sp, nullptr, &utf_len) == (unsigned int)c ) { - // Check if a < entity ini html matches a UTF-8 character in the - // search string. - //printf("%ld unicode match 0x%02X 0x%02X\n", bp-value_, *sp, c); - sp += utf_len; - bp = vanilla(bp+1, b->end); - } else if (*sp == c) { - // Check if UTF-8 bytes in html and the search string match. - //printf("%ld binary match %c/%c\n", bp-value_, *sp, c); - sp++; - bp = vanilla(bp+1, b->end); - } else { - // No match, so reset to start of search... . - //printf("reset search (%c/%c)\n", *sp, c); - sp = s; - bp = bs = vanilla(bs+1, b->end); - } - } - - if (!*sp) { // Found a match! - topline(b->y - b->h); - return int(bs - value_); - } - } - - // No match! - return (-1); -} - -/** \brief Formats the help text. */ void Fl_Help_View::format() { @@ -2478,86 +1868,6 @@ void Fl_Help_View::format_table( /** - \brief Frees memory used for the document. - */ -void Fl_Help_View::free_data() { - // Release all images... - if (value_) { - const char *ptr, // Pointer into block - *attrs; // Pointer to start of element attributes - HV_Edit_Buffer buf; // Text buffer - char attr[1024], // Attribute buffer - wattr[1024], // Width attribute buffer - hattr[1024]; // Height attribute buffer - - DEBUG_FUNCTION(__LINE__,__FUNCTION__); - - for (ptr = value_; *ptr;) - { - if (*ptr == '<') - { - ptr ++; - - if (strncmp(ptr, "!--", 3) == 0) - { - // Comment... - ptr += 3; - if ((ptr = strstr(ptr, "-->")) != nullptr) - { - ptr += 3; - continue; - } - else - break; - } - - buf.clear(); - - while (*ptr && *ptr != '>' && !isspace((*ptr)&255)) - buf += *ptr++; - - attrs = ptr; - while (*ptr && *ptr != '>') - ptr ++; - - if (*ptr == '>') - ptr ++; - - if (buf.cmp("IMG")) - { - Fl_Shared_Image *img; - int width; - int height; - - get_attr(attrs, "WIDTH", wattr, sizeof(wattr)); - get_attr(attrs, "HEIGHT", hattr, sizeof(hattr)); - width = get_length(wattr); - height = get_length(hattr); - - if (get_attr(attrs, "SRC", attr, sizeof(attr))) { - // Get and release the image to free it from memory... - img = get_image(attr, width, height); - if ((void*)img != &broken_image) { - img->release(); - } - } - } - } - else - ptr++; - } - - free((void *)value_); - value_ = 0; - } - - blocks_ .clear(); - link_list_.clear(); - target_line_map_.clear(); -} - - -/** \brief Gets an alignment attribute. \param[in] p Pointer to start of attributes. \param[in] a Default alignment. @@ -2854,111 +2164,86 @@ int Fl_Help_View::get_length(const char *l) { return val; } +// ---- Text selection -std::shared_ptr<Fl_Help_View::Link> Fl_Help_View::find_link(int xx, int yy) -{ - for (auto &link : link_list_) { - if (link->box.contains(xx, yy)) { - return link; - } - } - return nullptr; -} +/* + About selecting text: -void Fl_Help_View::follow_link(std::shared_ptr<Link> linkp) -{ - clear_selection(); - set_changed(); - std::string target = linkp->target;; // Current target + Still to do: + - &word; style characters mess up our count inside a word boundary + - we can only select words, no individual characters + - no dragging of the selection into another widget + - we are using the draw() function to measure screen postion of text + by rerouting the code via draw_mode_. Some drawing functions are + still called which is slow and requires a fake graphics context. + It may help to get rid of those calls if not in DRAW mode. - if ( (linkp->filename_ != filename_) && !linkp->filename_.empty() ) { - // Load the new document, if the filename is different - std::string url; - size_t directory_scheme_length = url_scheme(directory_); - size_t filename_scheme_length = url_scheme(linkp->filename_); - if ( (directory_scheme_length > 0) && (filename_scheme_length == 0) ) { - // If directory_ starts with a scheme (e.g.ftp:), but linkp->filename_ does not: - if (linkp->filename_[0] == '/') { - // If linkp->filename_ is absolute... - url = directory_.substr(0, directory_scheme_length) + linkp->filename_;; - } else { - // If linkp->filename_ is relative, the URL is the directory_ plus the filename - url = directory_ + "/" + linkp->filename_; - } - } else if (linkp->filename_[0] != '/' && (filename_scheme_length == 0)) { - // If the filename is relative and does not start with a scheme (ftp: , etc.)... - if (!directory_.empty()) { - // If we have a current directory, use that as the base for the URL - url = directory_ + "/" + linkp->filename_; - } else { - // If we do not have a current directory, use the application's current working directory - char dir[FL_PATH_MAX]; // Current directory (static size ok until we have fl_getcwd_std() - fl_getcwd(dir, sizeof(dir)); - url = "file:" + std::string(dir) + "/" + linkp->filename_; - } - } else { - // If the filename is absolute or starts with a protocol (e.g.ftp:), use it as is - url = linkp->filename_; - } + matt. + */ - // If a target is specified, append it to the URL - if (!linkp->target.empty()) { - url += "#" + linkp->target; - } +/** + \brief Draws a text string in the help view. - load(url.c_str()); + This function draws the text string \p t at position (\p x, \p y) in the help view. + If the text is selected, it draws a selection rectangle around it and changes the text color. - } else if (!target.empty()) { - // Keep the same document, scroll to the target line - topline(target.c_str()); + \param[in] t Text to draw + \param[in] x X position to draw at + \param[in] y Y position to draw at + \param[in] entity_extra_length (unclear) + */ +void Fl_Help_View::hv_draw(const char *t, int x, int y, int entity_extra_length) +{ + if (draw_mode_ == Mode::DRAW) { + if (selected_ && current_pos_<selection_last_ && current_pos_>=selection_first_) { + Fl_Color c = fl_color(); + fl_color(tmp_selection_color_); + int w = (int)fl_width(t); + if (current_pos_+(int)strlen(t)<selection_last_) + w += (int)fl_width(' '); + fl_rectf(x, y+fl_descent()-fl_height(), w, fl_height()); + fl_color(selection_text_color_); + fl_draw(t, x, y); + fl_color(c); + } else { + fl_draw(t, x, y); + } } else { - // No target, no filename, just scroll to the top of the document - topline(0); + // If draw_mode_ is not DRAW, we don't actually draw anything, but instead + // measure where text blocks are on screen during a mouse selection process. + int w = (int)fl_width(t); + if ( (Fl::event_x() >= x) && (Fl::event_x() < x+w) ) { + if ( (Fl::event_y() >= y-fl_height()+fl_descent()) && (Fl::event_y() <= y+fl_descent()) ) { + int f = (int) current_pos_; + int l = (int) (f+strlen(t)); // use 'quote_char' to calculate the true length of the HTML string + if (draw_mode_ == Mode::PUSH) { + selection_push_first_ = f; + selection_push_last_ = l; + } else { // Mode::DRAG + selection_drag_first_ = f; + selection_drag_last_ = l + entity_extra_length; + } + } + } } - - // Scroll the content horizontally to the left - leftline(0); } /** - \brief Removes the current text selection. -*/ -void Fl_Help_View::clear_selection() -{ - selected_ = false; - selection_first_ = 0; - selection_last_ = 0; - redraw(); -} + \brief Called from `handle()>FL_PUSH`, starts new text selection process. + This method return 1 if the user clicks on selectable text. It sets + selection_push_first_ and selection_push_last_ to the current + selection start and end positions, respectively. -/** - \brief Selects all the text in the view. + \return 1 if the selection was started, 0 if not. */ -void Fl_Help_View::select_all() -{ - clear_global_selection(); - if (!value_) return; - selection_drag_last_ = selection_last_ = (int) strlen(value_); - selected_ = true; -} - - -void Fl_Help_View::clear_global_selection() +char Fl_Help_View::begin_selection() { - if (selected_) redraw(); + clear_selection(); selection_push_first_ = selection_push_last_ = 0; selection_drag_first_ = selection_drag_last_ = 0; - selection_first_ = selection_last_ = 0; - selected_ = false; -} - - -char Fl_Help_View::begin_selection() -{ - clear_global_selection(); if (!fl_help_view_buffer) fl_help_view_buffer = fl_create_offscreen(1, 1); @@ -2975,6 +2260,10 @@ char Fl_Help_View::begin_selection() } +/** + \brief Called from `handle()>FL_DRAG`, extending text selection. + \return 1 if more than just the initial text is selected. +*/ char Fl_Help_View::extend_selection() { if (Fl::event_is_click()) @@ -3025,31 +2314,11 @@ char Fl_Help_View::extend_selection() } } -// convert a command with up to four letters into an unsigned int -static uint32_t command(const char *cmd) -{ - uint32_t ret = (tolower(cmd[0])<<24); - char c = cmd[1]; - if (c=='>' || c==' ' || c==0) return ret; - ret |= (tolower(c)<<16); - c = cmd[2]; - if (c=='>' || c==' ' || c==0) return ret; - ret |= (tolower(c)<<8); - c = cmd[3]; - if (c=='>' || c==' ' || c==0) return ret; - ret |= tolower(c); - c = cmd[4]; - if (c=='>' || c==' ' || c==0) return ret; - return 0; -} - - -static constexpr uint32_t CMD(char a, char b, char c, char d) -{ - return ((a<<24)|(b<<16)|(c<<8)|d); -} - +/** + \brief Called from `handle()>FL_RELEASE`, ends text selection process. + This method clears the static selection helper member variables. +*/ void Fl_Help_View::end_selection() { selection_push_first_ = 0; @@ -3059,108 +2328,642 @@ void Fl_Help_View::end_selection() } -/** - \brief Check if the user selected text in this view. - \return 1 if text is selected, 0 if no text is selected - */ -int Fl_Help_View::text_selected() { - return selected_; -} +// ------ Fl_Help_View Protected and Public methods +// ---- Widget management /** - \brief If text is selected in this view, copy it to a clipboard. - \param[in] clipboard for x11 only, 0=selection buffer, 1=clipboard, 2=both - \return 1 if text is selected, 0 if no text is selected - */ -int Fl_Help_View::copy(int clipboard) { - if (!selected_) - return 0; + \brief Draws the Fl_Help_View widget. +*/ +void Fl_Help_View::draw() +{ + int i; // Looping var + const Text_Block *block; // Pointer to current block + const char *ptr, // Pointer to text in block + *attrs; // Pointer to start of element attributes + HV_Edit_Buffer buf; // Text buffer + char attr[1024]; // Attribute buffer + int xx, yy, ww, hh; // Current positions and sizes + int line; // Current line + Fl_Font font; + Fl_Fontsize fsize; // Current font and size + Fl_Color fcolor; // current font color + int head, pre, // Flags for text + needspace; // Do we need whitespace? + Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; + // Box to draw... + int underline, // Underline text? + xtra_ww; // Extra width for underlined space between words - // convert the select part of our html text into some kind of somewhat readable UTF-8 - // and store it in the selection buffer - int p = 0; - char pre = 0; - int len = (int) strlen(value_); - char *txt = (char*)malloc(len+1), *d = txt; - const char *s = value_, *cmd, *src; - for (;;) { - int c = (*s++) & 0xff; - if (c==0) break; - if (c=='<') { // begin of some html command. Skip until we find a '>' - cmd = s; - for (;;) { - c = (*s++) & 0xff; - if (c==0 || c=='>') break; - } - if (c==0) break; - // do something with this command... . - // The replacement string must not be longer than the command - // itself plus '<' and '>' - src = 0; - switch (command(cmd)) { - case CMD('p','r','e', 0 ): pre = 1; break; - case CMD('/','p','r','e'): pre = 0; break; - case CMD('t','d', 0 , 0 ): - case CMD('p', 0 , 0 , 0 ): - case CMD('/','p', 0 , 0 ): - case CMD('b','r', 0 , 0 ): src = "\n"; break; - case CMD('l','i', 0 , 0 ): src = "\n * "; break; - case CMD('/','h','1', 0 ): - case CMD('/','h','2', 0 ): - case CMD('/','h','3', 0 ): - case CMD('/','h','4', 0 ): - case CMD('/','h','5', 0 ): - case CMD('/','h','6', 0 ): src = "\n\n"; break; - case CMD('t','r', 0 , 0 ): - case CMD('h','1', 0 , 0 ): - case CMD('h','2', 0 , 0 ): - case CMD('h','3', 0 , 0 ): - case CMD('h','4', 0 , 0 ): - case CMD('h','5', 0 , 0 ): - case CMD('h','6', 0 , 0 ): src = "\n\n"; break; - case CMD('d','t', 0 , 0 ): src = "\n "; break; - case CMD('d','d', 0 , 0 ): src = "\n - "; break; + DEBUG_FUNCTION(__LINE__,__FUNCTION__); + + // Draw the scrollbar(s) and box first... + ww = w(); + hh = h(); + i = 0; + + draw_box(b, x(), y(), ww, hh, bgcolor_); + + if ( hscrollbar_.visible() || scrollbar_.visible() ) { + int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); + int hor_vis = hscrollbar_.visible(); + int ver_vis = scrollbar_.visible(); + // Scrollbar corner + int scorn_x = x() + ww - (ver_vis?scrollsize:0) - Fl::box_dw(b) + Fl::box_dx(b); + int scorn_y = y() + hh - (hor_vis?scrollsize:0) - Fl::box_dh(b) + Fl::box_dy(b); + if ( hor_vis ) { + if ( hscrollbar_.h() != scrollsize ) { // scrollsize changed? + hscrollbar_.resize(x(), scorn_y, scorn_x - x(), scrollsize); + init_sizes(); } - int n = (int) (s-value_); - if (src && n>selection_first_ && n<=selection_last_) { - while (*src) { - *d++ = *src++; - } - c = src[-1] & 0xff; - p = isspace(c) ? ' ' : c; + draw_child(hscrollbar_); + hh -= scrollsize; + } + if ( ver_vis ) { + if ( scrollbar_.w() != scrollsize ) { // scrollsize changed? + scrollbar_.resize(scorn_x, y(), scrollsize, scorn_y - y()); + init_sizes(); } - continue; + draw_child(scrollbar_); + ww -= scrollsize; } - const char *s2 = s; - if (c=='&') { // special characters (HTML entities) - int xx = quote_char(s); - if (xx >= 0) { - c = xx; - for (;;) { - char cc = *s++; - if (!cc || cc==';') break; + if ( hor_vis && ver_vis ) { + // Both scrollbars visible? Draw little gray box in corner + fl_color(FL_GRAY); + fl_rectf(scorn_x, scorn_y, scrollsize, scrollsize); + } + } + + if (!value_) + return; + + if (selected_) { + if (Fl::focus() == this) { + // If this widget has the focus, we use the selection color directly + tmp_selection_color_ = selection_color(); + } else { + // Otherwise we blend the selection color with the background color + tmp_selection_color_ = fl_color_average(bgcolor_, selection_color(), 0.8f); + } + selection_text_color_ = fl_contrast(textcolor_, tmp_selection_color_); + } + current_pos_ = 0; + + // Clip the drawing to the inside of the box... + fl_push_clip(x() + Fl::box_dx(b), y() + Fl::box_dy(b), + ww - Fl::box_dw(b), hh - Fl::box_dh(b)); + fl_color(textcolor_); + + // Draw all visible blocks... + for (i = 0, block = &blocks_[0]; i < (int)blocks_.size(); i ++, block ++) + if ((block->y + block->h) >= topline_ && block->y < (topline_ + h())) + { + line = 0; + xx = block->line[line]; + yy = block->y - topline_; + hh = 0; + pre = 0; + head = 0; + needspace = 0; + underline = 0; + + initfont(font, fsize, fcolor); + // byte length difference between html entity (encoded by &...;) and + // UTF-8 encoding of same character + int entity_extra_length = 0; + for (ptr = block->start, buf.clear(); ptr < block->end;) + { + if ((*ptr == '<' || isspace((*ptr)&255)) && buf.size() > 0) + { + if (!head && !pre) + { + // Check width... + ww = buf.width(); + + if (needspace && xx > block->x) + xx += (int)fl_width(' '); + + if ((xx + ww) > block->w) + { + if (line < 31) + line ++; + xx = block->line[line]; + yy += hh; + hh = 0; + } + + hv_draw(buf.c_str(), xx + x() - leftline_, yy + y(), entity_extra_length); + buf.clear(); + entity_extra_length = 0; + if (underline) { + xtra_ww = isspace((*ptr)&255)?(int)fl_width(' '):0; + fl_xyline(xx + x() - leftline_, yy + y() + 1, + xx + x() - leftline_ + ww + xtra_ww); + } + current_pos_ = (int) (ptr-value_); + + xx += ww; + if ((fsize + 2) > hh) + hh = fsize + 2; + + needspace = 0; + } + else if (pre) + { + while (isspace((*ptr)&255)) + { + if (*ptr == '\n') + { + hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); + if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, + xx + x() - leftline_ + buf.width()); + buf.clear(); + current_pos_ = (int) (ptr-value_); + if (line < 31) + line ++; + xx = block->line[line]; + yy += hh; + hh = fsize + 2; + } + else if (*ptr == '\t') + { + // Do tabs every 8 columns... + buf += ' '; // add at least one space + while (buf.size() & 7) + buf += ' '; + } + else { + buf += ' '; + } + if ((fsize + 2) > hh) + hh = fsize + 2; + + ptr ++; + } + + if (buf.size() > 0) + { + hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); + ww = buf.width(); + buf.clear(); + if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, + xx + x() - leftline_ + ww); + xx += ww; + current_pos_ = (int) (ptr-value_); + } + + needspace = 0; + } + else + { + buf.clear(); + + while (isspace((*ptr)&255)) + ptr ++; + current_pos_ = (int) (ptr-value_); + } + } + + if (*ptr == '<') + { + ptr ++; + + if (strncmp(ptr, "!--", 3) == 0) + { + // Comment... + ptr += 3; + if ((ptr = strstr(ptr, "-->")) != nullptr) + { + ptr += 3; + continue; + } + else + break; + } + + while (*ptr && *ptr != '>' && !isspace((*ptr)&255)) + buf += *ptr++; + + attrs = ptr; + while (*ptr && *ptr != '>') + ptr ++; + + if (*ptr == '>') + ptr ++; + + // end of command reached, set the supposed start of printed eord here + current_pos_ = (int) (ptr-value_); + if (buf.cmp("HEAD")) + head = 1; + else if (buf.cmp("BR")) + { + if (line < 31) + line ++; + xx = block->line[line]; + yy += hh; + hh = 0; + } + else if (buf.cmp("HR")) + { + fl_line(block->x + x(), yy + y(), block->w + x(), + yy + y()); + + if (line < 31) + line ++; + xx = block->line[line]; + yy += 2 * fsize;//hh; + hh = 0; + } + else if (buf.cmp("CENTER") || + buf.cmp("P") || + buf.cmp("H1") || + buf.cmp("H2") || + buf.cmp("H3") || + buf.cmp("H4") || + buf.cmp("H5") || + buf.cmp("H6") || + buf.cmp("UL") || + buf.cmp("OL") || + buf.cmp("DL") || + buf.cmp("LI") || + buf.cmp("DD") || + buf.cmp("DT") || + buf.cmp("PRE")) + { + if (tolower(buf[0]) == 'h') + { + font = FL_HELVETICA_BOLD; + fsize = textsize_ + '7' - buf[1]; + } + else if (buf.cmp("DT")) + { + font = textfont_ | FL_ITALIC; + fsize = textsize_; + } + else if (buf.cmp("PRE")) + { + font = FL_COURIER; + fsize = textsize_; + pre = 1; + } + + if (buf.cmp("LI")) + { + if (block->ol) { + char buf[10]; + snprintf(buf, sizeof(buf), "%d. ", block->ol_num); + hv_draw(buf, xx - (int)fl_width(buf) + x() - leftline_, yy + y()); + } + else { + // draw bullet (•) Unicode: U+2022, UTF-8 (hex): e2 80 a2 + unsigned char bullet[4] = { 0xe2, 0x80, 0xa2, 0x00 }; + hv_draw((char *)bullet, xx - fsize + x() - leftline_, yy + y()); + } + } + + pushfont(font, fsize); + buf.clear(); + } + else if (buf.cmp("A") && + get_attr(attrs, "HREF", attr, sizeof(attr)) != nullptr) + { + fl_color(linkcolor_); + underline = 1; + } + else if (buf.cmp("/A")) + { + fl_color(textcolor_); + underline = 0; + } + else if (buf.cmp("FONT")) + { + if (get_attr(attrs, "COLOR", attr, sizeof(attr)) != nullptr) { + textcolor_ = get_color(attr, textcolor_); + } + + if (get_attr(attrs, "FACE", attr, sizeof(attr)) != nullptr) { + if (!strncasecmp(attr, "helvetica", 9) || + !strncasecmp(attr, "arial", 5) || + !strncasecmp(attr, "sans", 4)) font = FL_HELVETICA; + else if (!strncasecmp(attr, "times", 5) || + !strncasecmp(attr, "serif", 5)) font = FL_TIMES; + else if (!strncasecmp(attr, "symbol", 6)) font = FL_SYMBOL; + else font = FL_COURIER; + } + + if (get_attr(attrs, "SIZE", attr, sizeof(attr)) != nullptr) { + if (isdigit(attr[0] & 255)) { + // Absolute size + fsize = (int)(textsize_ * pow(1.2, atof(attr) - 3.0)); + } else { + // Relative size + fsize = (int)(fsize * pow(1.2, atof(attr) - 3.0)); + } + } + + pushfont(font, fsize); + } + else if (buf.cmp("/FONT")) + { + popfont(font, fsize, textcolor_); + } + else if (buf.cmp("U")) + underline = 1; + else if (buf.cmp("/U")) + underline = 0; + else if (buf.cmp("B") || + buf.cmp("STRONG")) + pushfont(font |= FL_BOLD, fsize); + else if (buf.cmp("TD") || + buf.cmp("TH")) + { + int tx, ty, tw, th; + + if (tolower(buf[1]) == 'h') + pushfont(font |= FL_BOLD, fsize); + else + pushfont(font = textfont_, fsize); + + tx = block->x - 4 - leftline_; + ty = block->y - topline_ - fsize - 3; + tw = block->w - block->x + 7; + th = block->h + fsize - 5; + + if (tx < 0) + { + tw += tx; + tx = 0; + } + + if (ty < 0) + { + th += ty; + ty = 0; + } + + tx += x(); + ty += y(); + + if (block->bgcolor != bgcolor_) + { + fl_color(block->bgcolor); + fl_rectf(tx, ty, tw, th); + fl_color(textcolor_); + } + + if (block->border) + fl_rect(tx, ty, tw, th); + } + else if (buf.cmp("I") || + buf.cmp("EM")) + pushfont(font |= FL_ITALIC, fsize); + else if (buf.cmp("CODE") || + buf.cmp("TT")) + pushfont(font = FL_COURIER, fsize); + else if (buf.cmp("KBD")) + pushfont(font = FL_COURIER_BOLD, fsize); + else if (buf.cmp("VAR")) + pushfont(font = FL_COURIER_ITALIC, fsize); + else if (buf.cmp("/HEAD")) + head = 0; + else if (buf.cmp("/H1") || + buf.cmp("/H2") || + buf.cmp("/H3") || + buf.cmp("/H4") || + buf.cmp("/H5") || + buf.cmp("/H6") || + buf.cmp("/B") || + buf.cmp("/STRONG") || + buf.cmp("/I") || + buf.cmp("/EM") || + buf.cmp("/CODE") || + buf.cmp("/TT") || + buf.cmp("/KBD") || + buf.cmp("/VAR")) + popfont(font, fsize, fcolor); + else if (buf.cmp("/PRE")) + { + popfont(font, fsize, fcolor); + pre = 0; + } + else if (buf.cmp("IMG")) + { + Fl_Shared_Image *img = 0; + int width, height; + char wattr[8], hattr[8]; + + + get_attr(attrs, "WIDTH", wattr, sizeof(wattr)); + get_attr(attrs, "HEIGHT", hattr, sizeof(hattr)); + width = get_length(wattr); + height = get_length(hattr); + + if (get_attr(attrs, "SRC", attr, sizeof(attr))) { + img = get_image(attr, width, height); + if (!width) width = img->w(); + if (!height) height = img->h(); + } + + if (!width || !height) { + if (get_attr(attrs, "ALT", attr, sizeof(attr)) == nullptr) { + strcpy(attr, "IMG"); + } + } + + ww = width; + + if (needspace && xx > block->x) + xx += (int)fl_width(' '); + + if ((xx + ww) > block->w) + { + if (line < 31) + line ++; + + xx = block->line[line]; + yy += hh; + hh = 0; + } + + if (img) { + img->draw(xx + x() - leftline_, + yy + y() - fl_height() + fl_descent() + 2); + } + + xx += ww; + if ((height + 2) > hh) + hh = height + 2; + + needspace = 0; + } + buf.clear(); + } + else if (*ptr == '\n' && pre) + { + hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); + buf.clear(); + + if (line < 31) + line ++; + xx = block->line[line]; + yy += hh; + hh = fsize + 2; + needspace = 0; + + ptr ++; + current_pos_ = (int) (ptr-value_); + } + else if (isspace((*ptr)&255)) + { + if (pre) + { + if (*ptr == ' ') + buf += ' '; + else + { + // Do tabs every 8 columns... + buf += ' '; // at least one space + while (buf.size() & 7) + buf += ' '; + } + } + + ptr ++; + if (!pre) current_pos_ = (int) (ptr-value_); + needspace = 1; + } + else if (*ptr == '&') // process html entity + { + ptr ++; + + int qch = quote_char(ptr); + + if (qch < 0) + buf += '&'; + else { + size_t utf8l = buf.size(); + buf.add(qch); + utf8l = buf.size() - utf8l; // length of added UTF-8 text + const char *oldptr = ptr; + ptr = strchr(ptr, ';') + 1; + entity_extra_length += int(ptr - (oldptr-1)) - utf8l; // extra length between html entity and UTF-8 + } + + if ((fsize + 2) > hh) + hh = fsize + 2; + } + else + { + buf += *ptr++; + + if ((fsize + 2) > hh) + hh = fsize + 2; } } - } - int n = (int) (s2-value_); - if (n>selection_first_ && n<=selection_last_) { - if (!pre && c < 256 && isspace(c)) c = ' '; - if (p != ' ' || c != ' ') { - if (s2 != s) { // c was an HTML entity - d += fl_utf8encode(c, d); + + if (buf.size() > 0 && !pre && !head) + { + ww = buf.width(); + + if (needspace && xx > block->x) + xx += (int)fl_width(' '); + + if ((xx + ww) > block->w) + { + if (line < 31) + line ++; + xx = block->line[line]; + yy += hh; + hh = 0; } - else *d++ = c; } - p = c; + + if (buf.size() > 0 && !head) + { + hv_draw(buf.c_str(), xx + x() - leftline_, yy + y()); + if (underline) fl_xyline(xx + x() - leftline_, yy + y() + 1, + xx + x() - leftline_ + ww); + current_pos_ = (int) (ptr-value_); + } } - if (n>selection_last_) break; // stop parsing html after end of selection - } - *d = 0; - Fl::copy(txt, (int) strlen(txt), clipboard); - // printf("copy [%s]\n", txt); - free(txt); - return 1; + + fl_pop_clip(); +} // draw() + + +/** + \brief Creates the Fl_Help_View widget at the specified position and size. + \param[in] xx, yy, ww, hh Position and size of the widget + \param[in] l Label for the widget, can be nullptr +*/ +Fl_Help_View::Fl_Help_View(int xx, int yy, int ww, int hh, const char *l) +: Fl_Group(xx, yy, ww, hh, l), + scrollbar_(xx + ww - Fl::scrollbar_size(), yy, Fl::scrollbar_size(), hh - Fl::scrollbar_size()), + hscrollbar_(xx, yy + hh - Fl::scrollbar_size(), ww - Fl::scrollbar_size(), Fl::scrollbar_size()) +{ + color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR); + + title_[0] = '\0'; + defcolor_ = FL_FOREGROUND_COLOR; + bgcolor_ = FL_BACKGROUND_COLOR; + textcolor_ = FL_FOREGROUND_COLOR; + linkcolor_ = FL_SELECTION_COLOR; + textfont_ = FL_TIMES; + textsize_ = 12; + value_ = nullptr; + + blocks_.clear(); + + link_ = (Fl_Help_Func *)0; + + link_list_.clear(); + + directory_.clear(); + filename_.clear(); + + topline_ = 0; + leftline_ = 0; + size_ = 0; + hsize_ = 0; + + selection_mode_ = Mode::DRAW; + selected_ = false; + selection_first_ = 0; + selection_last_ = 0; + + scrollbar_size_ = 0; + + scrollbar_.value(0, hh, 0, 1); + scrollbar_.step(8.0); + scrollbar_.show(); + scrollbar_.callback( [](Fl_Widget *s, void *u) { + ((Fl_Help_View*)u)->topline((int)(((Fl_Scrollbar*)s)->value())); + }, this ); + + hscrollbar_.value(0, ww, 0, 1); + hscrollbar_.step(8.0); + hscrollbar_.show(); + hscrollbar_.type(FL_HORIZONTAL); + hscrollbar_.callback( [](Fl_Widget *s, void *u) { + ((Fl_Help_View*)u)->leftline(int(((Fl_Scrollbar*)s)->value())); + }, this ); + + end(); + + resize(xx, yy, ww, hh); +} + + +/** + \brief Destroys the Fl_Help_View widget. + + The destructor destroys the widget and frees all memory that has been + allocated for the current document. +*/ +Fl_Help_View::~Fl_Help_View() +{ + clear_selection(); + free_data(); } @@ -3291,154 +3094,55 @@ int Fl_Help_View::handle(int event) /** - \brief Creates the Fl_Help_View widget at the specified position and size. - \param[in] xx, yy, ww, hh Position and size of the widget - \param[in] l Label for the widget, can be nullptr -*/ -Fl_Help_View::Fl_Help_View(int xx, int yy, int ww, int hh, const char *l) -: Fl_Group(xx, yy, ww, hh, l), - scrollbar_(xx + ww - Fl::scrollbar_size(), yy, Fl::scrollbar_size(), hh - Fl::scrollbar_size()), - hscrollbar_(xx, yy + hh - Fl::scrollbar_size(), ww - Fl::scrollbar_size(), Fl::scrollbar_size()) + \brief Override the superclass's resize method. + \param[in] xx, yy, ww, hh New position and size of the widget + */ +void Fl_Help_View::resize(int xx, int yy, int ww, int hh) { - color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR); - - title_[0] = '\0'; - defcolor_ = FL_FOREGROUND_COLOR; - bgcolor_ = FL_BACKGROUND_COLOR; - textcolor_ = FL_FOREGROUND_COLOR; - linkcolor_ = FL_SELECTION_COLOR; - textfont_ = FL_TIMES; - textsize_ = 12; - value_ = nullptr; - - blocks_.clear(); - - link_ = (Fl_Help_Func *)0; - - link_list_.clear(); - - directory_.clear(); - filename_.clear(); - - topline_ = 0; - leftline_ = 0; - size_ = 0; - hsize_ = 0; - - selection_mode_ = Mode::DRAW; - selected_ = false; - selection_first_ = 0; - selection_last_ = 0; + Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; // Box to draw... - scrollbar_size_ = 0; + Fl_Widget::resize(xx, yy, ww, hh); - scrollbar_.value(0, hh, 0, 1); - scrollbar_.step(8.0); - scrollbar_.show(); - scrollbar_.callback( [](Fl_Widget *s, void *u) { - ((Fl_Help_View*)u)->topline((int)(((Fl_Scrollbar*)s)->value())); - }, this ); + int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); + scrollbar_.resize(x() + w() - scrollsize - Fl::box_dw(b) + Fl::box_dx(b), + y() + Fl::box_dy(b), scrollsize, h() - scrollsize - Fl::box_dh(b)); + hscrollbar_.resize(x() + Fl::box_dx(b), + y() + h() - scrollsize - Fl::box_dh(b) + Fl::box_dy(b), + w() - scrollsize - Fl::box_dw(b), scrollsize); + format(); +} - hscrollbar_.value(0, ww, 0, 1); - hscrollbar_.step(8.0); - hscrollbar_.show(); - hscrollbar_.type(FL_HORIZONTAL); - hscrollbar_.callback( [](Fl_Widget *s, void *u) { - ((Fl_Help_View*)u)->leftline(int(((Fl_Scrollbar*)s)->value())); - }, this ); - end(); +// ---- HTML source and raw data - resize(xx, yy, ww, hh); -} +/** + \brief Sets the current help text buffer to the string provided and reformats the text. + The provided character string \p val is copied internally and will be + freed when value() is called again, or when the widget is destroyed. -/** - \brief Destroys the Fl_Help_View widget. + If \p val is nullptr, then the widget is cleared. - The destructor destroys the widget and frees all memory that has been - allocated for the current document. + \param[in] val Text to view, or nullptr to clear the widget, + Fl_Help_View will creat a local copy of the string. */ -Fl_Help_View::~Fl_Help_View() +void Fl_Help_View::value(const char *val) { clear_selection(); free_data(); -} - - -/** - \brief Return the current filename for the text in the buffer. - - Fl_Help_View remains the owner of the allocated memory. If the filename - changes, the returned pointer will become stale. - - \return nullptr if the filename is empty -*/ -const char *Fl_Help_View::filename() const { - if (filename_.empty()) - return nullptr; - else - return filename_.c_str(); -} - - -/** - \brief Return the current directory for the text in the buffer. - - Fl_Help_View remains the owner of the allocated memory. If the directory - changes, the returned pointer will become stale. - - \return nullptr if the directory name is empty -*/ -const char *Fl_Help_View::directory() const { - if (directory_.empty()) - return nullptr; - else - return directory_.c_str(); -} - - -/** - \brief Return the title of the current document. - - Fl_Help_View remains the owner of the allocated memory. If the document - changes, the returned pointer will become stale. - - \return empty string if the directory name is empty - */ -const char *Fl_Help_View::title() const { - return title_.c_str(); -} - - -/** - \brief Set a callback function for following links. - - This method assigns a callback function to use when a link is - followed or a file is loaded (via Fl_Help_View::load()) that - requires a different file or path. - - The callback function receives a pointer to the Fl_Help_View - widget and the URI or full pathname for the file in question. - It must return a pathname that can be opened as a local file or nullptr: + set_changed(); - \code - const char *fn(Fl_Widget *w, const char *uri); - \endcode + if (!val) + return; - The link function can be used to retrieve remote or virtual - documents, returning a temporary file that contains the actual - data. If the link function returns nullptr, the value of - the Fl_Help_View widget will remain unchanged. + value_ = fl_strdup(val); - If the link callback cannot handle the URI scheme, it should - return the uri value unchanged or set the value() of the widget - before returning nullptr. + initial_load = 1; + format(); + initial_load = 0; - \param[in] fn Pointer to the callback function -*/ -void Fl_Help_View::link(Fl_Help_Func *fn) { - link_ = fn; + topline(0); + leftline(0); } @@ -3595,26 +3299,197 @@ int Fl_Help_View::load(const char *f) /** - \brief Override the superclass's resize method. - \param[in] xx, yy, ww, hh New position and size of the widget - */ -void Fl_Help_View::resize(int xx, int yy, int ww, int hh) + \brief Finds the specified string \p s at starting position \p p. + + The argument \p p and the return value are offsets in Fl_Help_View::value(), + counting from 0. If \p p is out of range, 0 is used. + + The string comparison is simple but honors some special cases: + - the specified string \p s must be in UTF-8 encoding + - HTML tags in value() are filtered (not compared as such, they never match) + - HTML entities like '\<' or '\&x#20ac;' are converted to Unicode (UTF-8) + - ASCII characters (7-bit, \< 0x80) are compared case insensitive + - every newline (LF, '\\n') in value() is treated like a single space + - all other strings are compared as-is (byte by byte) + + \param[in] s search string in UTF-8 encoding + \param[in] p starting position for search (0,...), Default = 0 + \return the matching position or -1 if not found +*/ +int Fl_Help_View::find(const char *s, int p) { - Fl_Boxtype b = box() ? box() : FL_DOWN_BOX; // Box to draw... + int i, // Looping var + c; // Current character + Text_Block *b; // Current block + const char *bp, // Block matching pointer + *bs, // Start of current comparison + *sp; // Search string pointer - Fl_Widget::resize(xx, yy, ww, hh); + DEBUG_FUNCTION(__LINE__,__FUNCTION__); - int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size(); - scrollbar_.resize(x() + w() - scrollsize - Fl::box_dw(b) + Fl::box_dx(b), - y() + Fl::box_dy(b), scrollsize, h() - scrollsize - Fl::box_dh(b)); - hscrollbar_.resize(x() + Fl::box_dx(b), - y() + h() - scrollsize - Fl::box_dh(b) + Fl::box_dy(b), - w() - scrollsize - Fl::box_dw(b), scrollsize); - format(); + // Range check input and value... + if (!s || !value_) return -1; + + if (p < 0 || p >= (int)strlen(value_)) p = 0; + + // Look for the string... + for (i = (int)blocks_.size(), b = &blocks_[0]; i > 0; i--, b++) { + if (b->end < (value_ + p)) + continue; + + if (b->start < (value_ + p)) + bp = value_ + p; + else + bp = b->start; + + bp = vanilla(bp, b->end); + if (bp == b->end) + continue; + + for (sp = s, bs = bp; *sp && *bp && bp < b->end; ) { + bool is_html_entity = false; + if (*bp == '&') { + // decode HTML entity... + if ((c = quote_char(bp + 1)) < 0) { + c = '&'; + } else { + const char *entity_end = strchr(bp + 1, ';'); + if (entity_end) { + is_html_entity = true; // c contains the unicode character + bp = entity_end; + } else { + c = '&'; + } + } + } else { + c = *bp; + } + + if (c == '\n') c = ' '; // treat newline as a single space + + // *FIXME* *UTF-8* (A.S. 02/14/2016) + // At this point c may be an arbitrary Unicode Code Point corresponding + // to a quoted character (see above), i.e. it _can_ be a multi byte + // UTF-8 sequence and must be compared with the corresponding + // multi byte string in (*sp)... + // For instance: "€" == 0x20ac -> 0xe2 0x82 0xac (UTF-8: 3 bytes). + // Hint: use fl_utf8encode() [see below] + + int utf_len = 1; + if (c > 0x20 && c < 0x80 && tolower(*sp) == tolower(c)) { + // Check for ASCII case insensitive match. + //printf("%ld text match %c/%c\n", bp-value_, *sp, c); + sp++; + bp = vanilla(bp+1, b->end); + } else if (is_html_entity && fl_utf8decode(sp, nullptr, &utf_len) == (unsigned int)c ) { + // Check if a < entity ini html matches a UTF-8 character in the + // search string. + //printf("%ld unicode match 0x%02X 0x%02X\n", bp-value_, *sp, c); + sp += utf_len; + bp = vanilla(bp+1, b->end); + } else if (*sp == c) { + // Check if UTF-8 bytes in html and the search string match. + //printf("%ld binary match %c/%c\n", bp-value_, *sp, c); + sp++; + bp = vanilla(bp+1, b->end); + } else { + // No match, so reset to start of search... . + //printf("reset search (%c/%c)\n", *sp, c); + sp = s; + bp = bs = vanilla(bs+1, b->end); + } + } + + if (!*sp) { // Found a match! + topline(b->y - b->h); + return int(bs - value_); + } + } + + // No match! + return (-1); +} + + +/** + \brief Set a callback function for following links. + + This method assigns a callback function to use when a link is + followed or a file is loaded (via Fl_Help_View::load()) that + requires a different file or path. + + The callback function receives a pointer to the Fl_Help_View + widget and the URI or full pathname for the file in question. + It must return a pathname that can be opened as a local file or nullptr: + + \code + const char *fn(Fl_Widget *w, const char *uri); + \endcode + + The link function can be used to retrieve remote or virtual + documents, returning a temporary file that contains the actual + data. If the link function returns nullptr, the value of + the Fl_Help_View widget will remain unchanged. + + If the link callback cannot handle the URI scheme, it should + return the uri value unchanged or set the value() of the widget + before returning nullptr. + + \param[in] fn Pointer to the callback function +*/ +void Fl_Help_View::link(Fl_Help_Func *fn) { + link_ = fn; +} + + +/** + \brief Return the current filename for the text in the buffer. + + Fl_Help_View remains the owner of the allocated memory. If the filename + changes, the returned pointer will become stale. + + \return nullptr if the filename is empty +*/ +const char *Fl_Help_View::filename() const { + if (filename_.empty()) + return nullptr; + else + return filename_.c_str(); } /** + \brief Return the current directory for the text in the buffer. + + Fl_Help_View remains the owner of the allocated memory. If the directory + changes, the returned pointer will become stale. + + \return nullptr if the directory name is empty +*/ +const char *Fl_Help_View::directory() const { + if (directory_.empty()) + return nullptr; + else + return directory_.c_str(); +} + + +/** + \brief Return the title of the current document. + + Fl_Help_View remains the owner of the allocated memory. If the document + changes, the returned pointer will become stale. + + \return empty string if the directory name is empty + */ +const char *Fl_Help_View::title() const { + return title_.c_str(); +} + + +// ---- Rendering attributes + +/** \brief Scroll the text to the given anchor. \param[in] anchor scroll to this named anchor */ @@ -3688,37 +3563,139 @@ void Fl_Help_View::leftline(int left) } -/** - \brief Sets the current help text buffer to the string provided and reformats the text. +// ---- Text selection - The provided character string \p val is copied internally and will be - freed when value() is called again, or when the widget is destroyed. +/** + \brief Removes the current text selection. +*/ +void Fl_Help_View::clear_selection() +{ + selected_ = false; + selection_first_ = 0; + selection_last_ = 0; + redraw(); +} - If \p val is nullptr, then the widget is cleared. - \param[in] val Text to view, or nullptr to clear the widget, - Fl_Help_View will creat a local copy of the string. +/** + \brief Selects all the text in the view. */ -void Fl_Help_View::value(const char *val) +void Fl_Help_View::select_all() { clear_selection(); - free_data(); - set_changed(); + if (!value_) return; + selection_drag_last_ = selection_last_ = (int) strlen(value_); + selected_ = true; +} - if (!val) - return; - value_ = fl_strdup(val); +/** + \brief Check if the user selected text in this view. + \return 1 if text is selected, 0 if no text is selected + */ +int Fl_Help_View::text_selected() { + return selected_; +} - initial_load = 1; - format(); - initial_load = 0; - topline(0); - leftline(0); +/** + \brief If text is selected in this view, copy it to a clipboard. + \param[in] clipboard for x11 only, 0=selection buffer, 1=clipboard, 2=both + \return 1 if text is selected, 0 if no text is selected + */ +int Fl_Help_View::copy(int clipboard) { + if (!selected_) + return 0; + + // convert the select part of our html text into some kind of somewhat readable UTF-8 + // and store it in the selection buffer + int p = 0; + char pre = 0; + int len = (int) strlen(value_); + char *txt = (char*)malloc(len+1), *d = txt; + const char *s = value_, *cmd, *src; + for (;;) { + int c = (*s++) & 0xff; + if (c==0) break; + if (c=='<') { // begin of some html command. Skip until we find a '>' + cmd = s; + for (;;) { + c = (*s++) & 0xff; + if (c==0 || c=='>') break; + } + if (c==0) break; + // do something with this command... . + // The replacement string must not be longer than the command + // itself plus '<' and '>' + src = 0; + switch (command(cmd)) { + case CMD('p','r','e', 0 ): pre = 1; break; + case CMD('/','p','r','e'): pre = 0; break; + case CMD('t','d', 0 , 0 ): + case CMD('p', 0 , 0 , 0 ): + case CMD('/','p', 0 , 0 ): + case CMD('b','r', 0 , 0 ): src = "\n"; break; + case CMD('l','i', 0 , 0 ): src = "\n * "; break; + case CMD('/','h','1', 0 ): + case CMD('/','h','2', 0 ): + case CMD('/','h','3', 0 ): + case CMD('/','h','4', 0 ): + case CMD('/','h','5', 0 ): + case CMD('/','h','6', 0 ): src = "\n\n"; break; + case CMD('t','r', 0 , 0 ): + case CMD('h','1', 0 , 0 ): + case CMD('h','2', 0 , 0 ): + case CMD('h','3', 0 , 0 ): + case CMD('h','4', 0 , 0 ): + case CMD('h','5', 0 , 0 ): + case CMD('h','6', 0 , 0 ): src = "\n\n"; break; + case CMD('d','t', 0 , 0 ): src = "\n "; break; + case CMD('d','d', 0 , 0 ): src = "\n - "; break; + } + int n = (int) (s-value_); + if (src && n>selection_first_ && n<=selection_last_) { + while (*src) { + *d++ = *src++; + } + c = src[-1] & 0xff; + p = isspace(c) ? ' ' : c; + } + continue; + } + const char *s2 = s; + if (c=='&') { // special characters (HTML entities) + int xx = quote_char(s); + if (xx >= 0) { + c = xx; + for (;;) { + char cc = *s++; + if (!cc || cc==';') break; + } + } + } + int n = (int) (s2-value_); + if (n>selection_first_ && n<=selection_last_) { + if (!pre && c < 256 && isspace(c)) c = ' '; + if (p != ' ' || c != ' ') { + if (s2 != s) { // c was an HTML entity + d += fl_utf8encode(c, d); + } + else *d++ = c; + } + p = c; + } + if (n>selection_last_) break; // stop parsing html after end of selection + } + *d = 0; + Fl::copy(txt, (int) strlen(txt), clipboard); + // printf("copy [%s]\n", txt); + free(txt); + return 1; } +// ---- Scroll bars + /** \brief Get the current size of the scrollbars' troughs, in pixels. @@ -3757,6 +3734,80 @@ void Fl_Help_View::scrollbar_size(int newSize) { } + + + + + + + + + + + + + + + + + + + + + + + +/** + \brief Skips over HTML tags in a text. + + In an html style text, set the character pointer p, skipping anything from a + leading '<' up to and including the closing '>'. If the end of the buffer is + reached, the function returns `end`. + + No need to handle UTF-8 here. + + \param[in] p pointer to html text, UTF-8 characters possible + \param[in] end pointer to the end of the text (need nut be NUL) + \return new pointer to text after skipping over '<...>' blocks, or `end` + if NUL was found or a '<...>' block was not closed. +*/ +static const char *vanilla(const char *p, const char *end) { + if (*p == '\0' || p >= end) return end; + for (;;) { + if (*p != '<') { + return p; + } else { + while (*p && p < end && *p != '>') p++; + } + p++; + if (*p == '\0' || p >= end) return end; + } +} + + + +// convert a command with up to four letters into an unsigned int +static uint32_t command(const char *cmd) +{ + uint32_t ret = (tolower(cmd[0])<<24); + char c = cmd[1]; + if (c=='>' || c==' ' || c==0) return ret; + ret |= (tolower(c)<<16); + c = cmd[2]; + if (c=='>' || c==' ' || c==0) return ret; + ret |= (tolower(c)<<8); + c = cmd[3]; + if (c=='>' || c==' ' || c==0) return ret; + ret |= tolower(c); + c = cmd[4]; + if (c=='>' || c==' ' || c==0) return ret; + return 0; +} + + +// ------ Some more helper functions + + /* \brief Returns the Unicode Code Point associated with a quoted character (aka "HTML Entity"). |
