From 6569bd349660e1f2f6c75dca00b40b6a9f9ea739 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:19:13 -0600 Subject: [PATCH 001/101] Update apos docs and code comments. --- centrallix/include/apos.h | 19 +- centrallix/wgtr/apos.c | 568 +++++++++++++++++++++++++++++++------- 2 files changed, 479 insertions(+), 108 deletions(-) diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index a40d3d26d..36f5e1ee4 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -115,23 +115,32 @@ int aposSpaceOutLines(pXArray, pXArray, int); /**Adjusts spaces between lines to int aposSnapWidgetsToGrid(pXArray, int); /**Refreshes widget dimensions to match adjusted grid**/ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree to process windows**/ -/** # defines **/ +/** # defines names for magic values to make them easier to read. **/ + +/** Indicates how a line links to a widget. */ #define APOS_SWIDGETS 1 #define APOS_EWIDGETS 2 +/*** Indicates if a section or line is a row (horizontal) or a column (vertical). + *** A row spans horizontally between two vertical lines, and a column spans + *** vertically between two horizontal lines. + ***/ #define APOS_ROW 1 #define APOS_COL 2 #define APOS_FUDGEFACTOR 0.5 -/** The greatest width between two widgets that still defines them as "adjacent," -*** indicating that we don't want to increase the distance between them **/ +/*** The greatest width between two widgets that still defines them as + *** "adjacent," indicating that we don't want to increase the distance + *** between them. Therefore, a section of this size or less is considered + *** a "spacer" which will not be resized. + ***/ #define APOS_MINSPACE 20 -/**Lowest acceptable width or height for a widget**/ +/** The lowest acceptable width or height for a widget. **/ #define APOS_MINWIDTH 30 -/**Default flexibilities for widgetless gaps in expanding or contracting applications **/ +/** Default flexibilities for widgetless gaps in expanding or contracting applications. **/ #define APOS_EGAPFLEX 30 #define APOS_CGAPFLEX 50 diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 8c3780205..b4719bca3 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -26,9 +26,59 @@ /* Author: Nathaniel Colson */ /* Creation: August 9, 2005 */ /* Description: Applies layout logic to the widgets of an application. */ -/* See centrallix-sysdoc/Auto-Positioning.md for more information. */ +/* See centrallix-sysdoc/Auto-Positioning.md for more information. */ /************************************************************************/ +/*** Author: Israel Fuller + *** Date: June, 2025 + *** + *** I wasn't the one to write most of this (although I did write a ton of + *** comments), but after doing my best to understand it, I hope that you will + *** find the compiled information below helpful. + *** + *** Execution of this file usually begins when wgtrVerify() in wgtr.c calls + *** aposAutoPositionWidgetTree(). To auto position the tree, the code first + *** it draws four lines on the four edges of every visible widget (with some + *** exceptions, @see aposAddLinesToGrid()). These lines divide the page into + *** into horizontal sections (rows) and vertical sections (columns) which span + *** the page. @see aposAddSectionsToGrid() for more detail. + *** + *** The program guesses that some of these sections are "spacers", which are + *** small amounts of space intended to provide visual room between widgets. + *** When resizing, these do not flex at all. However, many elements are able + *** to flex. @see aposSetFlexibilities() for more information about flexing. + *** + *** Next, the program uses aposSetLimits() to honor minimum and maximum sizes + *** of widgets, and finally calls aposAutoPositionContainers() to position + *** the widgets on the screen. Lastly, it calls aposProcessWindows() to handle + *** floating window widgets, which are typically ignored by most of the rest + *** of the code. + *** + *** Note: Due to this approach, this means that all sections and widgets start + *** and end at a line. The way these lines are set up ensures that start + *** lines are always on the top or left, and end lines are always on the + *** bottom or right. @see aposAddLinesForChildren() + *** + *** Notes: I wrote some information about various structs below that's good to + *** know. Some of this is covered elsewhere in the documentation. + *** + *** AposGrid: A data structure to store sections and lines. + *** + *** AposLine: An AposLine spans the entire page. + *** + *** AposSection: After lines are created, sections are added in between the + *** lines (aka. in between the nodes). Every node begins and ends on the + *** edge of a section, although it may span multiple sections. + *** + *** pWgtrNode: A pointer to a widget node instance. You can think of this + *** like a DOM node, but remember that it's common for them to expand + *** into multiple DOM nodes. Also, these can have children, just like + *** a DOM node, which is why a single widget node pointer is really + *** more of a tree of them. + *** + *** XArray: This array also stores its size (nAlloc) and the number of items + *** stored (nItems), so you don't have to pass that info separately. + ***/ #include #include @@ -37,6 +87,12 @@ #include "cxlib/xarray.h" #include "cxlib/datatypes.h" +/*** Allocate space for a grid, section, and line using the custom allocation + *** system. Note that register is similar to creating a new heap-allocated + *** variable, then binding it to a name. + *** + *** @returns 0, success. + ***/ int aposInit() { @@ -47,6 +103,14 @@ aposInit() return 0; } +/*** Dumps the grid content of a widget node and its floating children. This + *** function is most likely intended for debugging. + *** + *** @param tree The widget tree from which to extract the layout grid. + *** @param indent The number of 4-space indentations to indent the output. + *** Note: Included for the sake of recursion; just pass 0. + *** @returns 0, success. + ***/ int aposDumpGrid(pWgtrNode tree, int indent) { @@ -57,6 +121,7 @@ pWgtrNode child; printf("%*.*s*** %s ***\n", indent*4, indent*4, "", tree->Name); if (tree->LayoutGrid) { + /** Dump the grid rows. **/ sectionCnt = xaCount(&AGRID(tree->LayoutGrid)->Rows); for(i=0;iStartLine->Loc, section->Width); printf("\n"); } + + /** Dump the grid columns. **/ sectionCnt = xaCount(&AGRID(tree->LayoutGrid)->Cols); for(i=0;iChildren); for(i=0;iName); return -1; } - /** Set flexibilities on containers **/ + /** Set flexibilities on containers. **/ if (aposSetFlexibilities(tree) < 0) { return -1; @@ -110,7 +186,7 @@ int i=0, count=0; /*aposDumpGrid(tree, 0);*/ - /** Honor minimum/maximum space requirements **/ + /** Detect and honor minimum/maximum space requirements. **/ if (aposSetLimits(tree) < 0) { return -1; @@ -133,7 +209,7 @@ int i=0, count=0; /**makes a final pass through the tree and processes html windows**/ aposProcessWindows(tree, tree); - /**unpatches all of the heights that were specified in aposPrepareTree**/ + /** Unpatches the heights specified by aposPrepareTree(). **/ count=xaCount(&PatchedWidgets); for(i=0; ipre_height = -1; } + /** Free the PatchedWidgets XArray.**/ xaDeInit(&PatchedWidgets); return 0; } - +/*** Recursively sets flexibility values for containers and their children. + *** + *** @param Parent The parent node who's flexibilities are being set. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposSetFlexibilities(pWgtrNode Parent) { @@ -156,14 +237,14 @@ pWgtrNode Child; int i=0, childCount=xaCount(&(Parent->Children)); int sectCount; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - + /** Recursively set the flexibilities of all children. **/ for(i=0; iChildren), i); @@ -174,7 +255,7 @@ int sectCount; } } - /** Reset flexibility values in the grid **/ + /** Reset flexibility values in the grid. **/ if (theGrid && childCount > 0 && !(Parent->Flags & WGTR_F_NONVISUAL)) { sectCount = xaCount(&(theGrid->Rows)); @@ -191,7 +272,7 @@ int sectCount; } } - /**set the flexibility of the given container, if it is visual**/ + /** Set the flexibility of the given container, if it is visual. **/ if(!(Parent->Flags & WGTR_F_NONVISUAL)) if(aposSetContainerFlex(Parent) < 0) { @@ -202,8 +283,13 @@ int sectCount; return 0; } - -/** this function is the recursive function that actually does the work **/ +/*** Adjusts space to acomodate children, somehow? I think? + *** + *** @param Parent The widget node parent who's limits are being calculated. + *** @param delta_w The change in width required to accomodate children. + *** @param delta_h The change in height required to accomodate children. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposSetLimits_r(pWgtrNode Parent, int* delta_w, int* delta_h) { @@ -215,14 +301,14 @@ int sectionCount; pAposSection s; pWgtrNode Child; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /** Figure what is needed for children **/ + /** Calculate the total required space for children. **/ childCount = xaCount(&(Parent->Children)); total_child_delta_w = total_child_delta_h = 0; for(i=0;iLayoutGrid) { sectionCount = xaCount(&(AGRID(Parent->LayoutGrid)->Rows)); - for(i=0;iLayoutGrid)->Rows), i)); + /*** If it has a desired width, increase the height + *** enough to give it that width. + ***/ if (s->DesiredWidth >= 0) { *delta_h += (s->DesiredWidth - s->Width); @@ -263,6 +356,9 @@ pWgtrNode Child; for(i=0;iLayoutGrid)->Cols), i)); + /*** If it has a desired width, increase the width + *** enough to give it that width. + ***/ if (s->DesiredWidth >= 0) { *delta_w += (s->DesiredWidth - s->Width); @@ -273,7 +369,7 @@ pWgtrNode Child; } } - /** Make space for this widget bigger **/ + /** If there is extra space, expand this widget to fill that space. **/ if (*delta_w) { if (Parent->StartVLine && ALINE(Parent->StartVLine)->SSection) @@ -298,8 +394,11 @@ pWgtrNode Child; return 0; } - -/** This function simply call the recursive version **/ +/*** Adjusts space to acomodate children, somehow? I think? + *** + *** @param Parent The widget node parent who's limits are being calculated. + *** @returns 0, success. + ***/ int aposSetLimits(pWgtrNode Parent) { @@ -314,30 +413,40 @@ int rval; return 0; } - +/*** Patch children of the given Parent node with unspecified heights. Searches + *** recursively within containers. Patched children are logged in the given + *** PatchedWidgets array. + *** + *** @param Parent The parent node who's childen should be patched. + *** @param PatchedWidgets The widget children which have been patched. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposPrepareTree(pWgtrNode Parent, pXArray PatchedWidgets) { pWgtrNode Child; int i=0, childCount=xaCount(&(Parent->Children)); - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } + /** Loop through each child. **/ for(i=0; iChildren), i); - /**if a visual child has an unspecified height, patch it, unless it is a scrollpane**/ + /*** If a visual child has an unspecified height, patch it, unless it is in a scrollpane + *** Remember here that strcmp() returns 0 (false) if the strings are equal. + ***/ if((Child->height < 0) && !(Child->Flags & WGTR_F_NONVISUAL) && strcmp(Parent->Type, "widget/scrollpane")) aposPatchNegativeHeight(Child, PatchedWidgets); - /** If child is a container but not a window, recursively prepare it as well **/ + /** If child is a container, but not a floating window, recursively prepare it as well. **/ if((Child->Flags & WGTR_F_CONTAINER) && !(Child->Flags & WGTR_F_FLOATING)) if (aposPrepareTree(Child, PatchedWidgets) < 0) return -1; @@ -346,12 +455,18 @@ int i=0, childCount=xaCount(&(Parent->Children)); return 0; } +/*** Try to guess the height of a widget with an unspecified height. + *** + *** @param Widget The widget child who's height is unspecified. + *** @param PatchedWidgets The array to add the widget to after patching it. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposPatchNegativeHeight(pWgtrNode Widget, pXArray PatchedWidgets) { ObjData val; - /** set unspecified height of widget to an educated guess**/ + /** Try to guess the height based on the type of widget. **/ if(!strcmp(Widget->Type, "widget/editbox")) { Widget->height = 16; @@ -390,12 +505,21 @@ ObjData val; return 0; } + /** Add the widget to the provided array. **/ xaAddItem(PatchedWidgets, Widget); + + /** Overwrite the "prepositioning" height because it's most likely also invalid. **/ Widget->pre_height = Widget->height; return 0; } +/*** Calculates and sets the flexibility for a container by taking weighted + *** averages in each direction. + *** + *** @param W The container to be set. + *** @returns 0, success. + ***/ int aposSetContainerFlex(pWgtrNode W) { @@ -405,7 +529,8 @@ int i=0, sectCount=0, TotalWidth=0, ProductSum=0; if (!theGrid) return 0; - /**calculate average row flexibility, weighted by height **/ + /** Calculate average row flexibility, weighted by height. **/ + /** Note: Height is called width because rows are 1 dimentional. **/ sectCount = xaCount(&(theGrid->Rows)); for(i=0; iCols)); for(i=0; iType, "widget/scrollpane")); - /**set isWin to compensate windows' titlebars, if any**/ + /** Set isWin to compensate windows' titlebars, if any. **/ if(isWin && !strcmp(W->Type, "widget/childwindow")) { + /*** Set isWin (is window) to compensate for a titlebar. If the + *** node does not specify if it has a titlebar, assume it does. + ***/ if(wgtrGetPropertyValue(W, "titlebar", DATA_T_STRING, &val) < 0) - *isWin = 1; //if property not found, assume it has a titlebar + *isWin = 1; // Property not found, assume it has a titlebar. else *isWin = !strcmp(val.String, "yes"); } - /**isTopTab and isSideTab are used to compensate for tabs**/ + /** isTopTab and isSideTab are used to compensate for tabs. **/ if(isTopTab && !strcmp(W->Type, "widget/tab")) { - /**set isTopTab and isSideTab**/ + /*** Set isTopTab and isSideTab. If the node does not specify the + *** tab location, assume it has a top tab and leave side-tab unset. + **/ if(wgtrGetPropertyValue(W, "tab_location", DATA_T_STRING, &val) < 0) - *isTopTab = 1; //if property not found, assume top tab**/ + *isTopTab = 1; // Property not found, assume it has a top tab only. else { *isTopTab = (!strcmp(val.String, "top") || !strcmp(val.String, "bottom")); - *isSideTab = (!strcmp(val.String, "left") || (!strcmp(val.String, "right"))); + *isSideTab = (!strcmp(val.String, "left") || (!strcmp(val.String, "right"))); // Warning: Unchecked assignment. } - /**set tabWidth**/ + /** Set the tab width. If none is specified, default to 80. **/ if(wgtrGetPropertyValue(W, "tab_width", DATA_T_INTEGER, &val) < 0) *tabWidth = 80; else *tabWidth = val.Integer; @@ -467,7 +616,12 @@ ObjData val; return 0; } - +/*** Builds the layout grid for recursively for this container and all of its + *** children, including the lines and sections required for positioning. + *** + *** @param Parent The parent node who's grid is being built. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposBuildGrid(pWgtrNode Parent) { @@ -475,25 +629,24 @@ int childCount, i; pWgtrNode Child; pAposGrid theGrid = NULL; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /** Allocate a grid **/ + /** Allocate a grid. **/ if (Parent->Flags & WGTR_F_CONTAINER) { if (!(Parent->Flags & WGTR_F_NONVISUAL) || !Parent->Parent) { + /** Allocate and initialize a new pAposGrid. **/ theGrid = Parent->LayoutGrid = (pAposGrid)nmMalloc(sizeof(AposGrid)); if (!Parent->LayoutGrid) goto error; - - /**initiallize the XArrays in the grid**/ aposInitiallizeGrid(theGrid); - /**Add the lines to the grid**/ + /** Add lines for children to the grid. **/ if(aposAddLinesToGrid(Parent, &(theGrid->HLines), &(theGrid->VLines)) < 0) { mssError(0, "APOS", "aposBuildGrid: Couldn't add lines to %s's grid", @@ -501,7 +654,7 @@ pAposGrid theGrid = NULL; return -1; } - /**Add the sections to the grid**/ + /** Add the sections to the grid. **/ if(aposAddSectionsToGrid(theGrid, (Parent->height-Parent->pre_height), (Parent->width-Parent->pre_width)) < 0) @@ -512,7 +665,7 @@ pAposGrid theGrid = NULL; } } - /** Do it for all children of this widget **/ + /** Recursively build this grid for all children of this widget. **/ childCount = xaCount(&(Parent->Children)); for(i=0; iChildren)); for(i=0;ipre_height < Parent->min_height && Parent->min_height != 0) height_adj = Parent->min_height - Parent->pre_height; - /**Add the 2 horizontal border lines, unless parent is a scrollpane**/ + /** Add the 2 horizontal border lines, unless parent is a scrollpane. **/ if(strcmp(Parent->Type, "widget/scrollpane")) { if(aposCreateLine(NULL, HLines, 0, 0, 1, 0, 0) < 0) @@ -650,18 +838,19 @@ pXArray FirstCross, LastCross; if(aposCreateLine(NULL, HLines, (Parent->pre_height-isWin*24), 0, 1, height_adj, 0) < 0) goto CreateLineError; } - /**Add the 2 vertical border lines**/ - if(aposCreateLine(NULL, VLines, 0, 0, 1, 0, 1) < 0) + + /** Add the 2 vertical border lines. **/ goto CreateLineError; if(aposCreateLine(NULL, VLines, (Parent->pre_width-isSP*18), 0, 1, width_adj, 1) < 0) goto CreateLineError; + /** Recursively add the nonborder lines for all child nodes. **/ if(aposAddLinesForChildren(Parent, HLines, VLines) < 0) goto CreateLineError; - /**populate horizontal line cross XArrays**/ + /** Record the widgets that cross each horizontal line in its CWidgets XArray. **/ count = xaCount(HLines); - for(i=1; iCWidgets), &(CurrLine->EWidgets), &(CurrLine->CWidgets)); } - /**populate vertical line cross XArrays**/ + /** Record the widgets that cross each vertical line in its CWidgets XArray. **/ count = xaCount(VLines); - for(i=1; iCWidgets), &(CurrLine->EWidgets), &(CurrLine->CWidgets)); } - /**sanity check to make sure no widgets cross the border lines**/ - if(xaCount(HLines)) //don't test borderlines unless they exist + /** Sanity check to make sure no widgets cross the border lines. **/ + if(xaCount(HLines)) // Only check borderlines if they exist. { FirstCross = &(((pAposLine)xaGetItem(HLines, 0))->CWidgets); LastCross = &(((pAposLine)xaGetItem(HLines, (xaCount(HLines)-1)))->CWidgets); @@ -708,6 +897,16 @@ pXArray FirstCross, LastCross; return -1; } +/*** Adds 4 lines for the edges of each visual child. Searches nonvisual + *** containers recursively for qualifying grandchildren. Floating windows + *** are ignored. Scrollpanes recieve only 2 vertical lines (skipping their + *** horizontal edges). + *** + *** @param Parent The parent who's children are being given lines. + *** @param HLines The array to which horizontal lines should be added. + *** @param VLines The array to which vertical lines should be added. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposAddLinesForChildren(pWgtrNode Parent, pXArray HLines, pXArray VLines) { @@ -716,14 +915,14 @@ int isTopTab=0, isSideTab=0, tabWidth=0; int height_adj, width_adj; pWgtrNode C; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /**loop through the children and create 4 lines for each child's 4 edges**/ + /** Loop through the children and create 4 lines for each child's 4 edges. **/ for(i=0; iChildren), i); @@ -736,27 +935,36 @@ pWgtrNode C; if (C->pre_height < C->min_height && C->min_height != 0) height_adj = C->min_height - C->pre_height; - /** If C is a nonvisual container, add lines for - *** the grandchildren. Otherwise, if C is visual - *** and not a window, just add 4 lines for it **/ + /** If the child (C) is a nonvisual container, recursively add lines for any grandchildren. **/ if((C->Flags & WGTR_F_NONVISUAL) && (C->Flags & WGTR_F_CONTAINER)) { if (aposAddLinesForChildren(C, HLines, VLines) < 0) goto CreateLineError; } + /** Otherwise, if child (C) is visual and not a floating window, add 4 lines for it. **/ else if(!(C->Flags & WGTR_F_NONVISUAL) && !(C->Flags & WGTR_F_FLOATING)) { - /**add horizontal lines, unless parent is a scrollpane**/ + /** Add horizontal lines, unless parent is a scrollpane. **/ if(strcmp(Parent->Type, "widget/scrollpane")) { - if(aposCreateLine(C, HLines, (C->y), APOS_SWIDGETS, 0, 0, 0) < 0) + /*** From this code, we see that the start line is + *** always the minY, and the end of the line is + *** always the maxY. Thus, the top line is the + *** start line and the bottom line is the end line + *** because Y increases as we decend the page. + ***/ goto CreateLineError; if(aposCreateLine(C, HLines, (C->y + C->height + isTopTab*24), APOS_EWIDGETS, 0, height_adj, 0) < 0) goto CreateLineError; } - /**add vertical lines**/ - if(aposCreateLine(C, VLines, (C->x), APOS_SWIDGETS, 0, 0, 1) < 0) + /** Add vertical lines. **/ + /*** From this code, we see that the start line is always + *** the minX, and the end of the line is always the maxX. + *** Thus, the left line is the start line and the right + *** line is the end line because X increases as we move + *** right along the page. + ***/ goto CreateLineError; if(aposCreateLine(C, VLines, (C->x + C->width + isSideTab*tabWidth), APOS_EWIDGETS, 0, width_adj, 1) < 0) goto CreateLineError; @@ -770,12 +978,36 @@ pWgtrNode C; return -1; } +/*** Creates a new line in the grid or updates an existing line if it exists + *** in the same location. Remember that lines record the widgets that start + *** on them (SWidgets), end on them (EWidgets), and cross them (CWidgets). + *** + *** Note: This function all lines in the given array are oriented in the same + *** direction as the new line. At the time of this writing (June 2025), + *** all known calling functions upheld by maintaining an HLines and a + *** VLines array to store horizontal and vertical lines separately. + *** + *** @param Widget The widget which determined the location of this line, + *** which we add to the SWidgets or EWidgets array. + *** @param Lines The array that stores the lines. + *** @param Loc The location of the line. Only a single coordinate in + *** one dimension is needed since lines span the entire grid. + *** @param type The type, indicating whether the associated widget starts + *** or ends on this line. + *** @param isBorder A boolean that is true if this is a grid border line. + *** @param adj An adjustment added to or subtracted from the line to + *** satisfy min or max constraints (respectively). + *** @param is_vert A boolean that is true if this line is vertical. + *** See APOS_VERTICAL and APOS_HORIZONTAL. + *** + *** @returns 0, success. + ***/ int aposCreateLine(pWgtrNode Widget, pXArray Lines, int Loc, int type, int isBorder, int adj, int is_vert) { pAposLine Line = aposExistingLine(Lines, Loc); - /**if there is already a line, just upgrade it**/ + /** If there is already a line, we upgrade it instead of creating a new one. **/ if(Line != NULL) { @@ -789,14 +1021,14 @@ pAposLine Line = aposExistingLine(Lines, Loc); else { - /**otherwise, create and add the new line**/ + /** There's not already a line, so we allocate a new one. **/ if((Line = (pAposLine)nmMalloc(sizeof(AposLine))) < 0) { mssError(1, "APOS", "aposCreateLine: Couldn't allocate memory for new grid line"); return -1; } - /**initiallize new line**/ + /** Initiallize the new line. **/ memset(Line, 0, sizeof(AposLine)); xaInit(&(Line->SWidgets),16); xaInit(&(Line->EWidgets),16); @@ -807,11 +1039,11 @@ pAposLine Line = aposExistingLine(Lines, Loc); Line->SSection = NULL; Line->ESection = NULL; - /**add new line, sorted by location**/ + /** Add the new line, to the list of lines, sorted by location. **/ xaAddItemSortedInt32(Lines, Line, 0); } - /** Link the line and the widget together **/ + /** Link the line and the widget together. **/ if (type == APOS_SWIDGETS) { xaAddItem(&(Line->SWidgets), Widget); @@ -834,6 +1066,16 @@ pAposLine Line = aposExistingLine(Lines, Loc); return 0; } +/*** Gets a line from the array at the location, or returns NULL if none exists. + *** + *** Note: This function all lines in the given array are oriented in the same + *** direction. This is not tested, although at the time of this writing + *** (June, 2025), all calling functions upheld this contract. + *** + *** @param Lines The array of lines to search. + *** @param Loc The location to check for a line. + *** @returns A pointer to the line, if it exists, and NULL otherwise. + ***/ pAposLine aposExistingLine(pXArray Lines, int Loc) { @@ -848,36 +1090,64 @@ int i, count = xaCount(Lines); return NULL; } +/*** Detects if a widget in PrevList (usually the widgets that started in or + *** crossed the pevious line) ends on this line (aka. appears in EWidgets). + *** If it does not end on this line, we know it crosses this line, so we add + *** the widget to CWidgets. + *** + *** @param PrevList The list of previous widgets being checked. + *** @param EWidgets The list of widgets ending on the line in question. + *** @param CWidgets The list to which widgets that cross should be added. + *** @returns 0, success. + ***/ int aposFillInCWidget(pXArray PrevList, pXArray EWidgets, pXArray CWidgets) { pWgtrNode AddCandidate; int found=0, i=0, j=0, pCount=xaCount(PrevList), eCount=xaCount(EWidgets); - /** loop through the SWidgets or CWidgets array of the previous line**/ + /*** Loop through the array from the previous line. + *** Note: Could be that line's SWidgets OR CWidgets. + **/ for(i=0; iHLines)); for(i=1; iRows), ((pAposLine)xaGetItem(&(theGrid->HLines),(i-1))), @@ -887,7 +1157,7 @@ int count=0, i=0; return -1; } - /**Add columns**/ + /** Add column sections between vertical lines. **/ count = xaCount(&(theGrid->VLines)); for(i=1; iCols), ((pAposLine)xaGetItem(&(theGrid->VLines),(i-1))), @@ -900,15 +1170,29 @@ int count=0, i=0; return 0; } +/*** Calculate and set the flexibility value for a section. Spacers have 0 flex + *** and containers use the flex of their least flexible children. + *** + *** @param sect The section being set. + *** @param type The type of section (either APOS_ROW or APOS_COL). + *** @returns 0 if successful or -1 if a default value should be used instead + ***/ int aposSetSectionFlex(pAposSection sect, int type) { +/*** Note: + *** sCount + cCount includes all widgets intersecting this section because a + *** widget cannot begin inside a section. It always starts or eds at the edge + *** of a section. + ***/ int sCount = xaCount(&(sect->StartLine->SWidgets)); int cCount = xaCount(&(sect->StartLine->CWidgets)); - /** Set flex to 0 if the section is a spacer or contains non-flexible children, - *** otherwise set it to the average of the children. If none of those apply - *** it must be a wide, widgetless gap, assign a default flexibility **/ + /** Set flex to 0 if the section is a spacer or contains non-flexible + *** children, otherwise set it to the minimum of the children. If none + *** of those apply it must be a wide, widgetless gap. In this case, + *** return -1 to prompt the caller to determine a default flexibility. + ***/ if((sect->isSpacer) || (aposNonFlexChildren(sect->StartLine, type))) sect->Flex = 0; else if(sCount || cCount) @@ -919,12 +1203,21 @@ int cCount = xaCount(&(sect->StartLine->CWidgets)); return 0; } +/*** Creates a new row or column section between two lines in the grid. + *** + *** @param Sections The array of sections, to which this section will be added. + *** @param StartL The line which starts this section (typically the top/left line). + *** @param EndL The line which ends this section (typically the bottom/right line). + *** @param Diff I had a hard time figuring out what this means. + *** @param type Whether the section is a row (APOS_ROW) or a column (APAS_COL). + *** @returns 0 if successful, -1 otherwise. + ***/ int aposCreateSection(pXArray Sections, pAposLine StartL, pAposLine EndL, int Diff, int type) { pAposSection NewSect; - /**Allocate and initiallize a new section**/ + /** Allocate and initiallize a new section. **/ if((NewSect = (pAposSection)(nmMalloc(sizeof(AposSection)))) < 0) { mssError(1, "APOS", "nmMalloc(): Couldn't allocate memory for new row or column"); @@ -940,11 +1233,11 @@ pAposSection NewSect; StartL->SSection = NewSect; EndL->ESection = NewSect; - /** Need to adjust section width/height? **/ + /** Apply the adjustment from the end line, if needed. **/ if (EndL->Adj) NewSect->DesiredWidth = NewSect->Width + EndL->Adj; - /** Set section flexibility **/ + /** Set section flexibility. **/ if (aposSetSectionFlex(NewSect, type) < 0) { if (Diff < 0) @@ -958,22 +1251,36 @@ pAposSection NewSect; return 0; } +/*** Determines if a section between two lines is a spacer. + *** + *** If a section is a spacer, the assumption is that the designer probably put + *** that space there to provide visual breathing room in their design. Thus, we + *** should avoid resizing it as this may interfere with their design. + *** + *** @param StartL The line starting the section. (I think this is always the left/top.) + *** @param EndL The line starting the section. (I think this is always the right/bottom.) + *** @param type Whether the section is a row (APOS_ROW) or a column (APAS_COL). + *** @param isBorder Whether the section is on the border of the page. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposIsSpacer(pAposLine StartL, pAposLine EndL, int type, int isBorder) { pWgtrNode SW, EW; int i=0, j=0; +/** @brief The number of widgets starting at the end of this section.**/ int sCount=xaCount(&(EndL->SWidgets)); +/** @brief The number of widgets ending at the start of this section.**/ int eCount=xaCount(&(StartL->EWidgets)); - if((EndL->Loc - StartL->Loc) <= APOS_MINSPACE) //if section is sufficiently narrow + if((EndL->Loc - StartL->Loc) <= APOS_MINSPACE) // If section is sufficiently narrow. { - /**gap between border and widget**/ + /** Gaps between the border and any widget(s) are spacers. **/ if(isBorder && (sCount || eCount)) return 1; - /** Checks every widget ending on one side of the section against - *** every widget beginning on the other side to see if any of them - *** are directly across from each other **/ + /** Checks every widget ending on one side to see if a widget + *** starts directly across from it on the other side. + ***/ for(i=0; iSWidgets), i)); @@ -981,9 +1288,10 @@ int eCount=xaCount(&(StartL->EWidgets)); { EW = (pWgtrNode)(xaGetItem(&(StartL->EWidgets), j)); - /** if a corner of a widget on one side of the - *** section falls between the two corners of a widget - *** on the other side, return 1 **/ + /** If a corner of the widget on one side falls + *** between the two corners of a widget on the + *** other side, this is a spacer. + ***/ if((type == APOS_ROW) && (((EW->x >= SW->x) && (EW->x < (SW->x + SW->width))) || (((EW->x + EW->width) > SW->x) && ((EW->x + EW->width) <= (SW->x + SW->width))))) return 1; @@ -998,6 +1306,14 @@ int eCount=xaCount(&(StartL->EWidgets)); return 0; } +/*** Checks for any widgets starting on or crossing a line that are non-flexible + *** in the relevant dimention. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @returns 1 if any child widget is non-flexible in the relevant dimension, + *** 0 otherwise. + ***/ int aposNonFlexChildren(pAposLine L, int type) { @@ -1005,8 +1321,9 @@ int i=0; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** returns 1 if the widgets starting on or crossing the given - *** line have children that are completely non-flexible **/ + /*** Return 1 if the widgets starting on or crossing the given line have + *** children that are completely non-flexible. + ***/ if(type == APOS_ROW) { for(i=0; iCWidgets)); if(((pWgtrNode)(xaGetItem(&(L->CWidgets), i)))->fl_height == 0) return 1; } - else //type == APOS_COL + else // type == APOS_COL { for(i=0; iSWidgets), i)))->fl_width == 0) @@ -1029,6 +1346,12 @@ int cCount = xaCount(&(L->CWidgets)); return 0; } +/*** Calculates the average flexibility of widgets on a line. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @returns The average flexibility of children on the line. + ***/ int aposAverageChildFlex(pAposLine L, int type) { @@ -1036,7 +1359,7 @@ int TotalFlex=0, i=0; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** Sum the flexibilities of widgets within the section proceeding the line**/ + /** Sum the flexibilities. **/ if(type == APOS_ROW) { for(i=0; iCWidgets)); TotalFlex += ((pWgtrNode)xaGetItem(&(L->CWidgets), i))->fl_width; } - /**return average flexibility**/ + /** Return the average flexibility with an aditional fudge factor. **/ return (int)(APOS_FUDGEFACTOR + ((float)TotalFlex)/((float)sCount+(float)cCount)); } +/*** Calculates the minimum flexibility of widgets on a line. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @returns The minimum flexibility of children on the line. + ***/ int aposMinimumChildFlex(pAposLine L, int type) { @@ -1063,7 +1392,7 @@ int MinFlex=100, i=0, f; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** Find the min flex within the section proceeding the line**/ + /** Find the min flexibility. **/ if(type == APOS_ROW) { for(i=0; iCWidgets)); } } - /**return min flexibility**/ + /** Return the minimum flexibility. **/ return MinFlex; } +/*** Distributes extra or missing space among grid lines based on section flexibility. + *** + *** @param Lines The array of lines in the relevant direction on this grid. + *** @param Secctions The array of sections in the relevant direction on this grid. + *** @param Diff The space differencce from how the elements are currently spaced. + *** @returns The remaining space difference after spacing out elements as much as possible. + ***/ int aposSpaceOutLines(pXArray Lines, pXArray Sections, int Diff) { @@ -1105,7 +1441,7 @@ int FlexibleSections=0; float FlexWeight=0, SizeWeight=0; float TotalSum=0; - /**if there are no sections, don't bother going on**/ + /** If there are no sections, we have nothing to space out. **/ if(!count) return Diff; /**Sum the flexibilities of the sections**/ @@ -1118,9 +1454,13 @@ float TotalSum=0; FlexibleSections++; TotalFlexibleSpace += CurrSect->Width; } + else CurrSect->AdjWeight = 0.0f; } - /** if there is no flexibility for expansion or contraction return 0**/ + /*** If there is no flexibility (no expansion or contraction), we can't + *** space anything out. Return the original difference so this can be + *** spaced out elsewhere. + ***/ if(TotalFlex == 0) return Diff; /** sets each line's location equal to the previous line's location @@ -1201,6 +1541,14 @@ float TotalSum=0; return Extra; } +/*** Adjusts widget positions and sizes to snap them to grid lines. This + *** function should be called after updating grid lines to ensure that + *** widgets properly reflect the changes. + *** + *** @param Lines The lines being updated. + *** @param flag Either APOS_ROW or APOS_COL. + *** @returns 0, success. + ***/ int aposSnapWidgetsToGrid(pXArray Lines, int flag) { @@ -1231,6 +1579,7 @@ pWgtrNode Widget; aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); if(flag==APOS_ROW && Widget->fl_height) { + /** Calculate the new size, taking APOS_MINWIDTH into account.**/ newsize = CurrLine->Loc - Widget->y - isTopTab*24; if (newsize < APOS_MINWIDTH && Widget->pre_height >= APOS_MINWIDTH) Widget->height = APOS_MINWIDTH; @@ -1242,6 +1591,7 @@ pWgtrNode Widget; } else if(flag==APOS_COL && Widget->fl_width) { + /** Calculate the new size, taking APOS_MINWIDTH into account.**/ newsize = CurrLine->Loc - Widget->x - isSideTab*tabWidth; if (newsize < APOS_MINWIDTH && Widget->pre_width >= APOS_MINWIDTH) Widget->width = APOS_MINWIDTH; @@ -1257,6 +1607,14 @@ pWgtrNode Widget; return 0; } +/*** + *** Processes floating windows and recursively positions visual and + *** nonvisual containers. + *** + *** @param VisualRef The last visual container up the inheritance tree. + *** @param Parent The widget being scanned for windows. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposProcessWindows(pWgtrNode VisualRef, pWgtrNode Parent) { @@ -1359,6 +1717,10 @@ int ival; return 0; } +/*** Frees all memory used by a grid, including its lines and sections. + *** + *** @param theGrid The grid being freed. + ***/ int aposFree(pAposGrid theGrid) { From e3783389779c692a94c0fc7991c5ff397ae0c50a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:25:50 -0600 Subject: [PATCH 002/101] Improve code readability without affecting functionality. --- centrallix/include/apos.h | 9 +++++++++ centrallix/wgtr/apos.c | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index 36f5e1ee4..e99a90868 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -118,9 +118,14 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t /** # defines names for magic values to make them easier to read. **/ /** Indicates how a line links to a widget. */ +#define APOS_NOT_LINKED 0 #define APOS_SWIDGETS 1 #define APOS_EWIDGETS 2 +/** Indicates if a line is vertical. */ +#define APOS_VERTICAL 1 +#define APOS_HORIZONTAL 0 + /*** Indicates if a section or line is a row (horizontal) or a column (vertical). *** A row spans horizontally between two vertical lines, and a column spans *** vertically between two horizontal lines. @@ -128,6 +133,10 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t #define APOS_ROW 1 #define APOS_COL 2 +/** Indicates if a line is a border. */ +#define APOS_IS_BORDER 1 +#define APOS_NOT_BORDER 0 + #define APOS_FUDGEFACTOR 0.5 /*** The greatest width between two widgets that still defines them as diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index b4719bca3..49cfeb0db 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -833,15 +833,18 @@ pXArray FirstCross, LastCross; /** Add the 2 horizontal border lines, unless parent is a scrollpane. **/ if(strcmp(Parent->Type, "widget/scrollpane")) { - if(aposCreateLine(NULL, HLines, 0, 0, 1, 0, 0) < 0) + int minHeightLoc = 0, maxHeightLoc = Parent->pre_height - isWin * 24; + if(aposCreateLine(NULL, HLines, minHeightLoc, APOS_NOT_LINKED, APOS_IS_BORDER, 0, APOS_HORIZONTAL) < 0) goto CreateLineError; - if(aposCreateLine(NULL, HLines, (Parent->pre_height-isWin*24), 0, 1, height_adj, 0) < 0) + if(aposCreateLine(NULL, HLines, maxHeightLoc, APOS_NOT_LINKED, APOS_IS_BORDER, height_adj, APOS_HORIZONTAL) < 0) goto CreateLineError; } /** Add the 2 vertical border lines. **/ + int minWidthLoc = 0, maxWidthLoc = (Parent->pre_width-isSP*18); + if(aposCreateLine(NULL, VLines, minWidthLoc, APOS_NOT_LINKED, APOS_IS_BORDER, 0, APOS_VERTICAL) < 0) goto CreateLineError; - if(aposCreateLine(NULL, VLines, (Parent->pre_width-isSP*18), 0, 1, width_adj, 1) < 0) + if(aposCreateLine(NULL, VLines, maxWidthLoc, APOS_NOT_LINKED, APOS_IS_BORDER, width_adj, APOS_VERTICAL) < 0) goto CreateLineError; /** Recursively add the nonborder lines for all child nodes. **/ @@ -953,8 +956,10 @@ pWgtrNode C; *** start line and the bottom line is the end line *** because Y increases as we decend the page. ***/ + int minY = (C->y), maxY = (C->y + C->height + isTopTab*24); + if(aposCreateLine(C, HLines, minY, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_HORIZONTAL) < 0) goto CreateLineError; - if(aposCreateLine(C, HLines, (C->y + C->height + isTopTab*24), APOS_EWIDGETS, 0, height_adj, 0) < 0) + if(aposCreateLine(C, HLines, maxY, APOS_EWIDGETS, APOS_NOT_BORDER, height_adj, APOS_HORIZONTAL) < 0) goto CreateLineError; } @@ -965,8 +970,10 @@ pWgtrNode C; *** line is the end line because X increases as we move *** right along the page. ***/ + int minX = (C->x), maxX = (C->x + C->width + isSideTab*tabWidth); + if(aposCreateLine(C, VLines, minX, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_VERTICAL) < 0) goto CreateLineError; - if(aposCreateLine(C, VLines, (C->x + C->width + isSideTab*tabWidth), APOS_EWIDGETS, 0, width_adj, 1) < 0) + if(aposCreateLine(C, VLines, maxX, APOS_EWIDGETS, APOS_NOT_BORDER, width_adj, APOS_VERTICAL) < 0) goto CreateLineError; } } @@ -1470,9 +1477,7 @@ float TotalSum=0; { PrevSect = (pAposSection)xaGetItem(Sections, (i-1)); FlexWeight = (float)(PrevSect->Flex) / (float)(TotalFlex); - SizeWeight = 0; - if(FlexWeight > 0) - SizeWeight = (float)(PrevSect->Width) / (float)(TotalFlexibleSpace); + SizeWeight = (FlexWeight > 0) ? (float)(PrevSect->Width) / (float)(TotalFlexibleSpace) : 0; TotalSum += (FlexWeight * SizeWeight); } From 30394dff637cd7fb00a23813b80ed6fe90236025 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:32:59 -0600 Subject: [PATCH 003/101] Store adj weights for later CSS use. --- centrallix/include/apos.h | 21 +++++++++++++++++ centrallix/include/wgtr.h | 2 ++ centrallix/wgtr/apos.c | 48 ++++++++++++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index e99a90868..70bb49f12 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -51,6 +51,14 @@ struct _APOS_L XArray CWidgets; //widgets that cross the line pAposSection SSection; // section starting with this line pAposSection ESection; // section ending with this line + + /*** Used to find the distance the line should move when the parent + *** container is resized. LocAdjWeight is the weight that this line + *** moves relative to the container. MyAdjWeight is the amount that + *** this line moves relative to the line before (left of) it. + *** Used for generating responsive CSS. + ***/ + float LocAdjWeight, MyAdjWeight; }; /**Section Structure (used for both rows and columns)**/ @@ -63,6 +71,19 @@ struct _APOS_S int DesiredWidth; //When we need to resize to honor max/mins int isSpacer; //set for narrow spaces between widgets int isBorder; //set for grid border sections + + /*** Computed value which stores the weight for how much this section + *** should be adjusted when resizing. All sections along each dimention + *** inside a single page or other container should have a total adjWeight + *** of 1.0. + *** + *** For example, if we have two sections with adj weights of 0.4 and 0.6. + *** If the container/page is stretched by 10 px, then 4 px is distributed + *** to the first section and 6 px is distributed to the second section. + *** + *** Warning: Currently unused. + ***/ + float AdjWeight; }; /**Grid Structure**/ diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index 99f8c3760..eaf865ff4 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -102,6 +102,8 @@ typedef struct _WN int pre_x, pre_y, pre_width, pre_height; /** pre-layout geom. **/ int fl_x, fl_y, fl_width, fl_height;/** Flexibility **/ double fx, fy, fw, fh; /** internal flexibility calculations **/ + float xAdjWeight, yAdjWeight; /** Responsive CSS adjustment weights for x and y */ + float wAdjWeight, hAdjWeight; /** Responsive CSS adjustment weights for width and height */ int min_width, min_height; /** absolute minimums **/ int x, y, width, height; /** actual geometry **/ int top, bottom, left, right; /** container offsets **/ diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 49cfeb0db..d802fca9c 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -1482,6 +1482,10 @@ float TotalSum=0; TotalSum += (FlexWeight * SizeWeight); } + /** The initial borders do not adjust.**/ + pAposLine leftBorder = (pAposLine)xaGetItem(Lines, 0); + leftBorder->LocAdjWeight = leftBorder->MyAdjWeight = 0.0f; + for(i=1; i 0) SizeWeight = (float)(PrevSect->Width) / (float)(TotalFlexibleSpace); + + /*** Calculate the adjustment weight, and also save it so we can + *** replicate some of the following logic in the CSS we will + *** eventually send to the client. + ***/ + float AdjWeight = PrevSect->AdjWeight = (float)(FlexWeight*SizeWeight)/TotalSum; /**for expanding lines**/ if(Diff > 0) { - /*Adj = APOS_FUDGEFACTOR + (float)(Diff) * ((float)(FlexWeight+SizeWeight)/2.0);*/ - Adj = APOS_FUDGEFACTOR + (float)(Diff) * ((float)(FlexWeight*SizeWeight)/TotalSum); + /** Calculate adjustment using the adjustment weight. **/ + Adj = (float)(Diff) * AdjWeight + APOS_FUDGEFACTOR; + + /** Store the line adjustment weight for responsive CSS later.**/ + CurrLine->LocAdjWeight = PrevLine->LocAdjWeight + AdjWeight; + CurrLine->MyAdjWeight = AdjWeight; + + /** Apply the calculated adjustment.**/ CurrLine->Loc = PrevLine->Loc + PrevSect->Width + Adj; PrevSect->Width += Adj; } /**for contracting lines**/ else if(Diff < 0) { - /*Adj = (float)(Diff) * ((float)(FlexWeight+SizeWeight)/2.0) - APOS_FUDGEFACTOR;*/ - Adj = (float)(Diff) * ((float)(FlexWeight*SizeWeight)/TotalSum) - APOS_FUDGEFACTOR; + /** Calculate adjustment using the adjustment weight. **/ + Adj = (float)(Diff) * AdjWeight - APOS_FUDGEFACTOR; /** if the section width will be unacceptably *** narrow or negative after the adjustment **/ @@ -1572,8 +1588,14 @@ pWgtrNode Widget; for(j=0; jSWidgets), j); - if(flag == APOS_ROW) Widget->y = CurrLine->Loc; - else Widget->x = CurrLine->Loc; + if(flag == APOS_ROW) { + Widget->y = CurrLine->Loc; + Widget->yAdjWeight = CurrLine->LocAdjWeight; + } + else { + Widget->x = CurrLine->Loc; + Widget->xAdjWeight = CurrLine->LocAdjWeight; + } } /** Adjusts width or height of widgets ending on this line **/ @@ -1593,6 +1615,13 @@ pWgtrNode Widget; else /*Widget->height = APOS_MINWIDTH;*/ Widget->height = Widget->pre_height; + + /*** The widget copies the adjustment weight of the + *** line, ignoring APOS_MINWIDTH. This might lead + *** to problems down the road, but I plan to fix + *** them if and when I encounter them. + ***/ + Widget->hAdjWeight = CurrLine->MyAdjWeight; } else if(flag==APOS_COL && Widget->fl_width) { @@ -1605,6 +1634,13 @@ pWgtrNode Widget; else /*Widget->width = APOS_MINWIDTH;*/ Widget->width = Widget->pre_width; + + /*** The widget copies the adjustment weight of the + *** line, ignoring APOS_MINWIDTH. This might lead + *** to problems down the road, but I plan to fix + *** them if and when I encounter them. + ***/ + Widget->hAdjWeight = CurrLine->MyAdjWeight; } } } From 592b3f15851ebb3b13c9ad15048d9d94d0d8bc77 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:34:04 -0600 Subject: [PATCH 004/101] Add a helpful error value when attempting to write widget properties with an unknown type. --- centrallix/htmlgen/ht_render.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 285a1c0da..29b0152e7 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1419,6 +1419,10 @@ htr_internal_WriteWgtrProperty(pHtSession s, pWgtrNode tree, char* propname) xsDeInit(&proptxt); xsDeInit(&exptxt); break; + + default: + htrAddScriptWgtr_va(s, "%STR&SYM:'Unknown Datatype (%INT) - Add it in ht_render.c:htr_internal_WriteWgtrProperty()', ", propname, t); + break; } } } From 436da375f97b25fe437e2aed9db903b7b4d1311b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:35:32 -0600 Subject: [PATCH 005/101] Implement rendering doubles as widget properties. --- centrallix/htmlgen/ht_render.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 29b0152e7..a925d67fe 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1405,6 +1405,10 @@ htr_internal_WriteWgtrProperty(pHtSession s, pWgtrNode tree, char* propname) htrAddScriptWgtr_va(s, "%STR&SYM:%INT, ", propname, od.Integer); break; + case DATA_T_DOUBLE: + htrAddScriptWgtr_va(s, "%STR&SYM:%DBL, ", propname, od.Double); + break; + case DATA_T_STRING: htrAddScriptWgtr_va(s, "%STR&SYM:'%STR&JSSTR', ", propname, od.String); break; From 74d5f5f7bbacf845a99e034519d57fa7f96fe832 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 26 Jun 2025 17:36:49 -0600 Subject: [PATCH 006/101] Render the new adjustment weight doubles as widget properties for debugging. --- centrallix/htmlgen/ht_render.c | 4 ++++ centrallix/wgtr/wgtr.c | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index a925d67fe..f2555e697 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1496,6 +1496,10 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) htr_internal_WriteWgtrProperty(s, tree, "r_y"); htr_internal_WriteWgtrProperty(s, tree, "r_width"); htr_internal_WriteWgtrProperty(s, tree, "r_height"); + htr_internal_WriteWgtrProperty(s, tree, "adj_weight_x"); + htr_internal_WriteWgtrProperty(s, tree, "adj_weight_y"); + htr_internal_WriteWgtrProperty(s, tree, "adj_weight_w"); + htr_internal_WriteWgtrProperty(s, tree, "adj_weight_h"); } propname = wgtrFirstPropertyName(tree); while(propname) diff --git a/centrallix/wgtr/wgtr.c b/centrallix/wgtr/wgtr.c index 0293dfc62..fc7d48e79 100755 --- a/centrallix/wgtr/wgtr.c +++ b/centrallix/wgtr/wgtr.c @@ -1312,6 +1312,9 @@ wgtrGetPropertyType(pWgtrNode widget, char* name) !strcmp(name, "r_x") || !strcmp(name, "r_y") || !strcmp(name, "r_width") || !strcmp(name, "r_height") || !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height")) return DATA_T_INTEGER; + else if (!strcmp(name, "adj_weight_x") || !strcmp(name, "adj_weight_y") || + !strcmp(name, "adj_weight_w") || !strcmp(name, "adj_weight_h")) + return DATA_T_DOUBLE; count = xaCount(&(widget->Properties)); for (i=0;iInteger = widget->fl_height; return 0; } } } + if (datatype == DATA_T_DOUBLE) + { + if (!strncmp(name, "adj_weight_", 11)) + { + if (!strcmp(name+11, "x")) { val->Double = (double)widget->xAdjWeight; return 0; } + else if (!strcmp(name+11, "y")) { val->Double = (double)widget->yAdjWeight; return 0; } + else if (!strcmp(name+11, "w")) { val->Double = (double)widget->wAdjWeight; return 0; } + else if (!strcmp(name+11, "h")) { val->Double = (double)widget->hAdjWeight; return 0; } + } + } else if (datatype == DATA_T_STRING) { if (!strcmp(name, "name")) { val->String = widget->Name; return 0; } From bcc59875fc78e92f71527b930a56c0b9e4c5f324 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 27 Jun 2025 16:15:13 -0600 Subject: [PATCH 007/101] Reformat CSS. --- centrallix/htmlgen/ht_render.c | 59 ++++++++++++++++++++++++-------- centrallix/htmlgen/htdrv_pane.c | 60 +++++++++++++++++++++++++++------ 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index f2555e697..32eb99bdf 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2692,20 +2692,51 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y shadow_angle = strtod(strval, NULL); /** Generate the style CSS **/ - htrAddStylesheetItem_va(s, "\t%STR { left:%POSpx; top:%POSpx; %[width:%POSpx; %]%[height:%POSpx; %]%[z-index:%POS; %]%[color:%STR&CSSVAL; %]%[font-weight:bold; %]%[text-decoration:underline; %]%[font-style:italic; %]%[font:%STR&CSSVAL; %]%[font-size:%DBLpx; %]%[background-color:%STR&CSSVAL; %]%[background-image:url('%STR&CSSURL'); %]%[padding:%DBLpx; %]%[border:1px %STR&CSSVAL %STR&CSSVAL; %]%[border-radius:%DBLpx; %]%[text-align:%STR&CSSVAL; %]%[white-space:nowrap; %]%[box-shadow:%DBLpx %DBLpx %DBLpx %STR&CSSVAL%STR&CSSVAL; %]%[%STR %]}\n", - id, - x, y, w > 0, w, h > 0, h, z > 0, z, - *textcolor, textcolor, - !strcmp(style, "bold"), !strcmp(style, "underline"), !strcmp(style, "italic"), - *font, font, font_size > 0, font_size, - *bgcolor, bgcolor, *background, background, - padding > 0, padding, - *border_color, (*border_style)?border_style:"solid", border_color, border_radius > 0, border_radius, - *align, align, - !wrap, - (*shadow_color && shadow_radius > 0), sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color, (!strcasecmp(shadow_location,"inside"))?" inset":"", - addl && *addl, addl - ); + htrAddStylesheetItem_va(s, + "\t%STR {" + "left:%POSpx; " + "top:%POSpx; " + "%[width:%POSpx; %]" + "%[height:%POSpx; %]" + "%[z-index:%POS; %]" + "%[color:%STR&CSSVAL; %]" + "%[font-weight:bold; %]" + "%[text-decoration:underline; %]" + "%[font-style:italic; %]" + "%[font:%STR&CSSVAL; %]" + "%[font-size:%DBLpx; %]" + "%[background-color:%STR&CSSVAL; %]" + "%[background-image:url('%STR&CSSURL'); %]" + "%[padding:%DBLpx; %]" + "%[border:1px %STR&CSSVAL %STR&CSSVAL; %]" + "%[border-radius:%DBLpx; %]" + "%[text-align:%STR&CSSVAL; %]" + "%[white-space:nowrap; %]" + "%[box-shadow:%DBLpx %DBLpx %DBLpx %STR&CSSVAL%STR&CSSVAL; %]" + "%[%STR %]" + "}\n", + id, + x, + y, + (w > 0), w, + (h > 0), h, + (z > 0), z, + (*textcolor), textcolor, + (!strcmp(style, "bold")), + (!strcmp(style, "underline")), + (!strcmp(style, "italic")), + (*font), font, + (font_size > 0), font_size, + (*bgcolor), bgcolor, + (*background), background, + (padding > 0), padding, + (*border_color), (*border_style) ? border_style : "solid", border_color, + (border_radius > 0), border_radius, + (*align), align, + (!wrap), + (*shadow_color && shadow_radius > 0), sin(shadow_angle * M_PI/180) * shadow_offset, cos(shadow_angle * M_PI/180) *(-shadow_offset), shadow_radius, shadow_color, (!strcasecmp(shadow_location,"inside"))?" inset":"", + (addl && *addl), addl + ); return 0; } diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 0683cc6e6..64d5c0e68 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -154,24 +154,64 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) } /** Ok, write the style header items. **/ - if (style == 2) /* flat */ - { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-radius: %INTpx; %STR}\n",id,border_radius,main_bg); - } + int offset = 0; + if (style == 2) { /* flat, the default style, nothing to do */ } else if (style == 0 || style == 1) /* lowered or raised */ { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow: hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w-2*box_offset,h-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-style: solid; border-width: 1px; border-color: %STR %STR %STR %STR; border-radius: %INTpx; %STR}\n",id,c1,c2,c2,c1,border_radius,main_bg); + offset = -2 * box_offset; + htrAddStylesheetItem_va(s, + "\t#pn%POSmain {" + "border-style: solid; " + "border-width: 1px; " + "border-color: %STR %STR %STR %STR; " + "}\n", + id, + c1, c2, c2, c1 + ); } else if (style == 3) /* bordered */ { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow: hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w-2*box_offset,h-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-style: solid; border-width: 1px; border-color:%STR&CSSVAL; border-radius: %INTpx; %STR}\n",id,bdr,border_radius,main_bg); + offset = -2 * box_offset; + htrAddStylesheetItem_va(s, + "\t#pn%POSmain {" + "border-style: solid;" + "border-width: 1px; " + "border-color:%STR&CSSVAL; " + "}\n", + id, + bdr + ); } + + htrAddStylesheetItem_va(s, + "\t#pn%POSmain {" + "POSITION:absolute; " + "VISIBILITY:inherit; " + "overflow:hidden; " + "LEFT:%INTpx; " + "TOP:%INTpx; " + "WIDTH:%POSpx; " + "HEIGHT:%POSpx; " + "Z-INDEX:%POS; " + "border-radius: %INTpx;" + "%STR" + "}\n", + id, + x, + y, + w + offset, + h + offset, + z, + border_radius, + main_bg + ); + if (shadow_radius > 0) { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", id, shadow_offset, shadow_offset, shadow_radius, shadow_color); + htrAddStylesheetItem_va(s, + "\t#pn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", + id, shadow_offset, shadow_offset, shadow_radius, shadow_color + ); } /** DOM linkages **/ From 15b466fb7dfa58498f28d9d45e05bf6328019aa9 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 27 Jun 2025 16:20:47 -0600 Subject: [PATCH 008/101] Make design responsive by replacing px with %. --- centrallix/htmlgen/ht_render.c | 16 ++++++++-------- centrallix/htmlgen/htdrv_pane.c | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 32eb99bdf..084c38e65 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2694,10 +2694,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y /** Generate the style CSS **/ htrAddStylesheetItem_va(s, "\t%STR {" - "left:%POSpx; " - "top:%POSpx; " - "%[width:%POSpx; %]" - "%[height:%POSpx; %]" + "left:%DBL%%; " + "top:%DBL%%; " + "%[width:%DBL%%; %]" // BUG! + "%[height:%DBL%%; %]"// BUG! "%[z-index:%POS; %]" "%[color:%STR&CSSVAL; %]" "%[font-weight:bold; %]" @@ -2716,10 +2716,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - x, - y, - (w > 0), w, - (h > 0), h, + (double)x / node->Root->width * 100.0, + (double)y / node->Root->height * 100.0, + (w > 0), (double)w / node->Parent->width * 100.0, + (h > 0), (double)h / node->Parent->height * 100.0, (z > 0), z, (*textcolor), textcolor, (!strcmp(style, "bold")), diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 64d5c0e68..86db161d9 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -188,19 +188,19 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "POSITION:absolute; " "VISIBILITY:inherit; " "overflow:hidden; " - "LEFT:%INTpx; " - "TOP:%INTpx; " - "WIDTH:%POSpx; " - "HEIGHT:%POSpx; " + "LEFT:%DBL%%; " + "TOP:%DBL%%; " + "WIDTH:%DBL%%; " + "HEIGHT:%DBL%%; " "Z-INDEX:%POS; " "border-radius: %INTpx;" "%STR" "}\n", id, - x, - y, - w + offset, - h + offset, + (double)tree->x / tree->Root->width * 100.0, + (double)tree->y / tree->Root->height * 100.0, + (double)(w + offset) / tree->Parent->width * 100.0, + (double)(h + offset) / tree->Parent->height * 100.0, z, border_radius, main_bg From eaeeb5a2c043e4927bc7a55ddbf0c73d1598e82e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 27 Jun 2025 16:40:45 -0600 Subject: [PATCH 009/101] Reformat CSS for autolayout. --- centrallix/htmlgen/htdrv_autolayout.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index 0dee2198b..ae1321182 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -99,11 +99,26 @@ htalRender(pHtSession s, pWgtrNode tree, int z) strtcpy(name,wgtrGetName(tree),sizeof(name)); /** Add the stylesheet for the layer **/ - htrAddStylesheetItem_va(s,"\t#al%POSbase { POSITION:absolute; VISIBILITY:inherit; OVERFLOW:visible; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n", - //htrAddStylesheetItem_va(s,"\t#al%POSbase { POSITION:absolute; VISIBILITY:inherit; OVERFLOW:visible; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; CLIP:rect(%INTpx,%INTpx,%INTpx,%INTpx); Z-INDEX:%POS; }\n", - id,x,y,w,h, - //-1, w+1, h+1, -1, - z); + htrAddStylesheetItem_va(s, + "\t#al%POSbase {" + "POSITION:absolute; " + "VISIBILITY:inherit; " + "OVERFLOW:visible; " + "LEFT:%POSpx; " + "TOP:%POSpx; " + "WIDTH:%POSpx; " + "HEIGHT:%POSpx; " + //"CLIP:rect(%INTpx,%INTpx,%INTpx,%INTpx);" + "Z-INDEX:%POS; " + "}\n", + id, + x, + y, + w, + h, + //-1, w+1, h+1, -1, + z + ); /** Linkage **/ htrAddWgtrObjLinkage_va(s, tree, "al%POSbase",id); From 9e45033e2cd253c4a465e5341f3217f219a59ffb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 1 Jul 2025 13:37:08 -0600 Subject: [PATCH 010/101] Improve debugging. --- centrallix/htmlgen/ht_render.c | 4 ++++ centrallix/htmlgen/htdrv_pane.c | 4 ++-- centrallix/wgtr/wgtr.c | 12 ++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 084c38e65..7a5919f58 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1496,6 +1496,10 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) htr_internal_WriteWgtrProperty(s, tree, "r_y"); htr_internal_WriteWgtrProperty(s, tree, "r_width"); htr_internal_WriteWgtrProperty(s, tree, "r_height"); + htr_internal_WriteWgtrProperty(s, tree, "fl_x"); + htr_internal_WriteWgtrProperty(s, tree, "fl_y"); + htr_internal_WriteWgtrProperty(s, tree, "fl_width"); + htr_internal_WriteWgtrProperty(s, tree, "fl_height"); htr_internal_WriteWgtrProperty(s, tree, "adj_weight_x"); htr_internal_WriteWgtrProperty(s, tree, "adj_weight_y"); htr_internal_WriteWgtrProperty(s, tree, "adj_weight_w"); diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 86db161d9..e1503e8b5 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -197,8 +197,8 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "%STR" "}\n", id, - (double)tree->x / tree->Root->width * 100.0, - (double)tree->y / tree->Root->height * 100.0, + (double)x / tree->Root->width * 100.0, + (double)y / tree->Root->height * 100.0, (double)(w + offset) / tree->Parent->width * 100.0, (double)(h + offset) / tree->Parent->height * 100.0, z, diff --git a/centrallix/wgtr/wgtr.c b/centrallix/wgtr/wgtr.c index fc7d48e79..0fa312f19 100755 --- a/centrallix/wgtr/wgtr.c +++ b/centrallix/wgtr/wgtr.c @@ -1313,7 +1313,8 @@ wgtrGetPropertyType(pWgtrNode widget, char* name) !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height")) return DATA_T_INTEGER; else if (!strcmp(name, "adj_weight_x") || !strcmp(name, "adj_weight_y") || - !strcmp(name, "adj_weight_w") || !strcmp(name, "adj_weight_h")) + !strcmp(name, "adj_weight_w") || !strcmp(name, "adj_weight_h") || + !strcmp(name, "fx") || !strcmp(name, "fy") || !strcmp(name, "fw") || !strcmp(name, "fh")) return DATA_T_DOUBLE; count = xaCount(&(widget->Properties)); for (i=0;iDouble = widget->fx; return 0; } + else if (!strcmp(name+1, "y")) { val->Double = widget->fy; return 0; } + else if (!strcmp(name+1, "w")) { val->Double = widget->fw; return 0; } + else if (!strcmp(name+1, "h")) { val->Double = widget->fh; return 0; } + } + else if (!strncmp(name, "adj_weight_", 11)) { if (!strcmp(name+11, "x")) { val->Double = (double)widget->xAdjWeight; return 0; } else if (!strcmp(name+11, "y")) { val->Double = (double)widget->yAdjWeight; return 0; } From b6daf946d7b0f39205f5be08b13c1390bcea41a8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 1 Jul 2025 15:52:13 -0600 Subject: [PATCH 011/101] Add flexibility to autoscaling, using macros to make it more readable. --- centrallix/htmlgen/ht_render.c | 16 ++++++++-------- centrallix/htmlgen/htdrv_pane.c | 16 ++++++++-------- centrallix/include/ht_render.h | 4 ++++ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 7a5919f58..2139ae124 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2698,10 +2698,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y /** Generate the style CSS **/ htrAddStylesheetItem_va(s, "\t%STR {" - "left:%DBL%%; " - "top:%DBL%%; " - "%[width:%DBL%%; %]" // BUG! - "%[height:%DBL%%; %]"// BUG! + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[width:"ht_flex_format"; %]" // BUG! + "%[height:"ht_flex_format"; %]"// BUG! "%[z-index:%POS; %]" "%[color:%STR&CSSVAL; %]" "%[font-weight:bold; %]" @@ -2720,10 +2720,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - (double)x / node->Root->width * 100.0, - (double)y / node->Root->height * 100.0, - (w > 0), (double)w / node->Parent->width * 100.0, - (h > 0), (double)h / node->Parent->height * 100.0, + ht_flex(x, node->Parent->width, 100.0), + ht_flex(y, node->Parent->height, 100.0), + (w > 0), ht_flex(w, node->Parent->width, node->fl_width), + (h > 0), ht_flex(h, node->Parent->height, node->fl_height), (z > 0), z, (*textcolor), textcolor, (!strcmp(style, "bold")), diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index e1503e8b5..e6fe16338 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -188,19 +188,19 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "POSITION:absolute; " "VISIBILITY:inherit; " "overflow:hidden; " - "LEFT:%DBL%%; " - "TOP:%DBL%%; " - "WIDTH:%DBL%%; " - "HEIGHT:%DBL%%; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "HEIGHT:"ht_flex_format"; " "Z-INDEX:%POS; " "border-radius: %INTpx;" "%STR" "}\n", id, - (double)x / tree->Root->width * 100.0, - (double)y / tree->Root->height * 100.0, - (double)(w + offset) / tree->Parent->width * 100.0, - (double)(h + offset) / tree->Parent->height * 100.0, + ht_flex(x, tree->Parent->width, 100.0), + ht_flex(y, tree->Parent->height, 100.0), + ht_flex(w + offset, tree->Parent->width, tree->fl_width), + ht_flex(h + offset, tree->Parent->height, tree->fl_height), z, border_radius, main_bg diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index aeded08fe..7e6b20da0 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -362,5 +362,9 @@ int htrBuildClientWgtr(pHtSession s, pWgtrNode tree); /** For the rule module... **/ int htruleRegister(char* ruletype, ...); +/** My attempt to make flex code more readable. **/ +#define ht_flex_format "calc(%POSpx + (%DBL%% - %POSpx) * %DBL)" +#define ht_flex(size, total, flex) (size), (double)(size) / (total) * 100.0, (size), (double)(flex) / 100.0 + #endif /* _HT_RENDER_H */ From dbb1bbcf8f101537e7308562d6244f0349c64598 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Tue, 1 Jul 2025 17:12:26 -0600 Subject: [PATCH 012/101] Replace magic values for fl_x and fl_y with macros. --- centrallix/htmlgen/ht_render.c | 4 ++-- centrallix/htmlgen/htdrv_pane.c | 4 ++-- centrallix/include/ht_render.h | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 2139ae124..03473eb9b 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2720,8 +2720,8 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - ht_flex(x, node->Parent->width, 100.0), - ht_flex(y, node->Parent->height, 100.0), + ht_flex(x, node->Parent->width, ht_fl_x_compat), + ht_flex(y, node->Parent->height, ht_fl_y_compat), (w > 0), ht_flex(w, node->Parent->width, node->fl_width), (h > 0), ht_flex(h, node->Parent->height, node->fl_height), (z > 0), z, diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index e6fe16338..f9106a2c0 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -197,8 +197,8 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "%STR" "}\n", id, - ht_flex(x, tree->Parent->width, 100.0), - ht_flex(y, tree->Parent->height, 100.0), + ht_flex(x, tree->Parent->width, ht_fl_x_compat), + ht_flex(y, tree->Parent->height, ht_fl_y_compat), ht_flex(w + offset, tree->Parent->width, tree->fl_width), ht_flex(h + offset, tree->Parent->height, tree->fl_height), z, diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index 7e6b20da0..3ef7893f9 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -366,5 +366,12 @@ int htruleRegister(char* ruletype, ...); #define ht_flex_format "calc(%POSpx + (%DBL%% - %POSpx) * %DBL)" #define ht_flex(size, total, flex) (size), (double)(size) / (total) * 100.0, (size), (double)(flex) / 100.0 +/*** The widget fl_x and fl_y fields are never used in the generated layout. + *** Thus, the CSS I created to add responsiveness uses the values below + *** instead of the provided value to preserve backwards compatibility. + ***/ +#define ht_fl_x_compat 100.0 +#define ht_fl_y_compat 100.0 + #endif /* _HT_RENDER_H */ From 9b72f5f3cf608690f1cbe0a789a19a56b1ec1782 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 3 Jul 2025 15:34:08 -0600 Subject: [PATCH 013/101] Improve code documentation and readability. --- centrallix/include/apos.h | 3 ++- centrallix/include/ht_render.h | 6 +++--- centrallix/wgtr/apos.c | 26 ++++++++++++++++---------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index 70bb49f12..da8792946 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -158,12 +158,13 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t #define APOS_IS_BORDER 1 #define APOS_NOT_BORDER 0 +/** Allows rounding when casting floats or doubles to ints. */ #define APOS_FUDGEFACTOR 0.5 /*** The greatest width between two widgets that still defines them as *** "adjacent," indicating that we don't want to increase the distance *** between them. Therefore, a section of this size or less is considered - *** a "spacer" which will not be resized. + *** a "spacer" which will not be resized (aka. flex = 0). ***/ #define APOS_MINSPACE 20 diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index 3ef7893f9..e0448404f 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -362,9 +362,9 @@ int htrBuildClientWgtr(pHtSession s, pWgtrNode tree); /** For the rule module... **/ int htruleRegister(char* ruletype, ...); -/** My attempt to make flex code more readable. **/ -#define ht_flex_format "calc(%POSpx + (%DBL%% - %POSpx) * %DBL)" -#define ht_flex(size, total, flex) (size), (double)(size) / (total) * 100.0, (size), (double)(flex) / 100.0 +/** ===================================================== **/ +/** Define macros for implementing responsive dimensions. **/ +/** ===================================================== **/ /*** The widget fl_x and fl_y fields are never used in the generated layout. *** Thus, the CSS I created to add responsiveness uses the values below diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index d802fca9c..4b476038c 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -78,6 +78,11 @@ *** *** XArray: This array also stores its size (nAlloc) and the number of items *** stored (nItems), so you don't have to pass that info separately. + *** + *** SWidgets, CWidgets, and EWidgets: Lines record which widgets start, cross, + *** and end on them. These categories are exclusive, so a widget which + *** starts on a given line will be in the SWidgets list but it will not be + *** in the CWidgets list. ***/ #include @@ -529,8 +534,10 @@ int i=0, sectCount=0, TotalWidth=0, ProductSum=0; if (!theGrid) return 0; - /** Calculate average row flexibility, weighted by height. **/ - /** Note: Height is called width because rows are 1 dimentional. **/ + /*** Calculate average row flexibility, weighted by height. + *** Note: Section height is called width here because rows + *** are one dimentional and the feild is reused. + ***/ sectCount = xaCount(&(theGrid->Rows)); for(i=0; iFlex) / (float)(TotalFlex); - SizeWeight = 0; - - /** unless there's at least some flexibility, don't factor in size **/ - if(FlexWeight > 0) - SizeWeight = (float)(PrevSect->Width) / (float)(TotalFlexibleSpace); + SizeWeight = (FlexWeight > 0) ? (float)(PrevSect->Width) / (float)(TotalFlexibleSpace) : 0; /*** Calculate the adjustment weight, and also save it so we can *** replicate some of the following logic in the CSS we will @@ -1598,7 +1600,7 @@ pWgtrNode Widget; } } - /** Adjusts width or height of widgets ending on this line **/ + /** Adjusts width or height of widgets ending on this line. **/ count = xaCount(&(CurrLine->EWidgets)); for(j=0; jLoc - Widget->x - isSideTab*tabWidth; + + /** If the new size is now smaller than the minimum, clamp it. **/ if (newsize < APOS_MINWIDTH && Widget->pre_width >= APOS_MINWIDTH) Widget->width = APOS_MINWIDTH; + /** If the size is bigger than the minimum, or growing, that's fine. **/ else if (newsize >= APOS_MINWIDTH || newsize >= Widget->pre_width) Widget->width = newsize; + /** Otherwise, we can't update the size. **/ else /*Widget->width = APOS_MINWIDTH;*/ Widget->width = Widget->pre_width; From 019319d03e5fe8900fd5fedac9f5ca1d36a91784 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 3 Jul 2025 15:36:30 -0600 Subject: [PATCH 014/101] Fix flexibility a little, but it's still broken. --- centrallix/htmlgen/ht_render.c | 8 ++--- centrallix/htmlgen/htdrv_pane.c | 8 ++--- centrallix/include/ht_render.h | 54 +++++++++++++++++++++++++++++++-- centrallix/include/wgtr.h | 4 +-- centrallix/wgtr/apos.c | 31 +++++++++++++------ 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 03473eb9b..0a6844768 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2720,10 +2720,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - ht_flex(x, node->Parent->width, ht_fl_x_compat), - ht_flex(y, node->Parent->height, ht_fl_y_compat), - (w > 0), ht_flex(w, node->Parent->width, node->fl_width), - (h > 0), ht_flex(h, node->Parent->height, node->fl_height), + ht_flex(x, node->Parent->width, ht_get_fl_x(node)), + ht_flex(y, node->Parent->height, ht_get_fl_y(node)), + (w > 0), ht_flex(w, node->Parent->width, ht_get_fl_w(node)), + (h > 0), ht_flex(h, node->Parent->height, ht_get_fl_h(node)), (z > 0), z, (*textcolor), textcolor, (!strcmp(style, "bold")), diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index f9106a2c0..9d0f23416 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -197,10 +197,10 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "%STR" "}\n", id, - ht_flex(x, tree->Parent->width, ht_fl_x_compat), - ht_flex(y, tree->Parent->height, ht_fl_y_compat), - ht_flex(w + offset, tree->Parent->width, tree->fl_width), - ht_flex(h + offset, tree->Parent->height, tree->fl_height), + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w + offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h + offset, tree->Parent->height, ht_get_fl_h(tree)), z, border_radius, main_bg diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index e0448404f..d078ba1e0 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -370,8 +370,58 @@ int htruleRegister(char* ruletype, ...); *** Thus, the CSS I created to add responsiveness uses the values below *** instead of the provided value to preserve backwards compatibility. ***/ -#define ht_fl_x_compat 100.0 -#define ht_fl_y_compat 100.0 +/** @brief widget->fl_x is never used. Use this instead for compatibility. **/ +#define ht_fl_x_compat 1.0 +/** @brief widget->fl_y is never used. Use this instead for compatibility. **/ +#define ht_fl_y_compat 1.0 + +/** @brief The qprintf format to specify a responsive dimension. **/ +#define ht_flex_format "calc(%INTpx + (%DBL%% - %INTpx) * %DBL)" +/*** @brief The function which generates the values that should be passed to + *** qprintf in order to satisfy an ht_flex_format. + *** + *** @param size The original size of the ui element. + *** @param total The total size of the ui element's container. + *** @param flex The flexibility of the ui element. It is strongly recomended + *** to generate this with an ht_get_fl function call. + *** @returns Several values to serve as parameters for a qprintf call. + ***/ +#define ht_flex(size, total, flex) (size), (double)(size) / (total) * 100.0, (size), (flex) +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the x direction. + ***/ +#define ht_get_fl_x(widget) (ht_fl_x_compat) +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the y direction. + ***/ +#define ht_get_fl_y(widget) (ht_fl_y_compat) +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the width direction. + ***/ +#define ht_get_fl_w(widget) ((widget)->wAdjWeight) +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the height direction. + ***/ +#define ht_get_fl_h(widget) ((widget)->hAdjWeight) +/*** @brief A shortcut function to get the flexibility when writing the + *** LEFT CSS attribute. + *** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the left direction. + ***/ +#define ht_get_fl_l ht_get_fl_x +/*** @brief A shortcut function to get the flexibility when writing the + *** TOP CSS attribute. + *** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the top direction. + ***/ +#define ht_get_fl_t ht_get_fl_y + +/*** Alternate formula suggested in the GitHub Issue. I think this should work + *** and be more efficient, but for some reason it doesn't at all, and I can't + *** understand why. I probably just implemented it wrong. + ***/ +// #define ht_flex_format "calc(%INTpx + (100%% - %INTpx) * %DBL)" +// #define ht_flex(size, total, flex) (size), (total), (double)(flex) / 100.0 #endif /* _HT_RENDER_H */ diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index eaf865ff4..f11584f0d 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -102,8 +102,8 @@ typedef struct _WN int pre_x, pre_y, pre_width, pre_height; /** pre-layout geom. **/ int fl_x, fl_y, fl_width, fl_height;/** Flexibility **/ double fx, fy, fw, fh; /** internal flexibility calculations **/ - float xAdjWeight, yAdjWeight; /** Responsive CSS adjustment weights for x and y */ - float wAdjWeight, hAdjWeight; /** Responsive CSS adjustment weights for width and height */ + double xAdjWeight, yAdjWeight; /** Responsive CSS adjustment weights for x and y */ + double wAdjWeight, hAdjWeight; /** Responsive CSS adjustment weights for width and height */ int min_width, min_height; /** absolute minimums **/ int x, y, width, height; /** actual geometry **/ int top, bottom, left, right; /** container offsets **/ diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 4b476038c..619736703 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -1505,20 +1505,20 @@ float TotalSum=0; *** eventually send to the client. ***/ float AdjWeight = PrevSect->AdjWeight = (float)(FlexWeight*SizeWeight)/TotalSum; + + /** Store the line adjustment weight for responsive CSS later.**/ + CurrLine->LocAdjWeight = PrevLine->LocAdjWeight + AdjWeight; + CurrLine->MyAdjWeight = AdjWeight; /**for expanding lines**/ if(Diff > 0) { /** Calculate adjustment using the adjustment weight. **/ Adj = (float)(Diff) * AdjWeight + APOS_FUDGEFACTOR; - - /** Store the line adjustment weight for responsive CSS later.**/ - CurrLine->LocAdjWeight = PrevLine->LocAdjWeight + AdjWeight; - CurrLine->MyAdjWeight = AdjWeight; /** Apply the calculated adjustment.**/ - CurrLine->Loc = PrevLine->Loc + PrevSect->Width + Adj; PrevSect->Width += Adj; + CurrLine->Loc = PrevLine->Loc + PrevSect->Width; } /**for contracting lines**/ else if(Diff < 0) @@ -1551,8 +1551,8 @@ float TotalSum=0; } else { - CurrLine->Loc = PrevLine->Loc + PrevSect->Width + Adj; PrevSect->Width += Adj; + CurrLine->Loc = PrevLine->Loc + PrevSect->Width; } } } @@ -1623,11 +1623,11 @@ pWgtrNode Widget; *** to problems down the road, but I plan to fix *** them if and when I encounter them. ***/ - Widget->hAdjWeight = CurrLine->MyAdjWeight; + Widget->hAdjWeight += CurrLine->MyAdjWeight; } else if(flag==APOS_COL && Widget->fl_width) { - /** Calculate the new size, taking APOS_MINWIDTH into account.**/ + /** Calculate the new size, taking APOS_MINWIDTH into account. **/ newsize = CurrLine->Loc - Widget->x - isSideTab*tabWidth; /** If the new size is now smaller than the minimum, clamp it. **/ @@ -1646,9 +1646,22 @@ pWgtrNode Widget; *** to problems down the road, but I plan to fix *** them if and when I encounter them. ***/ - Widget->hAdjWeight = CurrLine->MyAdjWeight; + Widget->wAdjWeight += CurrLine->MyAdjWeight; } } + + /** Adjusts width or height of widgets ending on this line. **/ + count = xaCount(&(CurrLine->CWidgets)); + printf("Doing %d widgets.\n", count); + for(j=0; jCWidgets), j); + if(flag==APOS_ROW && Widget->fl_height) + Widget->hAdjWeight += CurrLine->MyAdjWeight; + else if(flag==APOS_COL && Widget->fl_width) + Widget->wAdjWeight += CurrLine->MyAdjWeight; + } + } return 0; From 36b5b0a11d5169667718f4ed58b82861b1cb114b Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 3 Jul 2025 15:37:24 -0600 Subject: [PATCH 015/101] Attempt to add responsiveness to widgets (doesn't quite work): autolayout, button, editbox, html, image, label, scrollpane, textbutton, & treeview. --- centrallix/htmlgen/htdrv_autolayout.c | 20 ++-- centrallix/htmlgen/htdrv_button.c | 100 +++++++++++++++--- centrallix/htmlgen/htdrv_editbox.c | 38 ++++++- centrallix/htmlgen/htdrv_html.c | 62 +++++++++-- centrallix/htmlgen/htdrv_image.c | 17 ++- centrallix/htmlgen/htdrv_label.c | 57 ++++++++-- centrallix/htmlgen/htdrv_scrollpane.c | 144 ++++++++++++++++++++++++-- centrallix/htmlgen/htdrv_textbutton.c | 48 ++++++--- centrallix/htmlgen/htdrv_treeview.c | 43 +++++++- 9 files changed, 458 insertions(+), 71 deletions(-) diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index ae1321182..66b7f83e0 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -100,23 +100,21 @@ htalRender(pHtSession s, pWgtrNode tree, int z) /** Add the stylesheet for the layer **/ htrAddStylesheetItem_va(s, - "\t#al%POSbase {" + "\t#al%POSbase { " "POSITION:absolute; " "VISIBILITY:inherit; " "OVERFLOW:visible; " - "LEFT:%POSpx; " - "TOP:%POSpx; " - "WIDTH:%POSpx; " - "HEIGHT:%POSpx; " - //"CLIP:rect(%INTpx,%INTpx,%INTpx,%INTpx);" + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "HEIGHT:"ht_flex_format"; " "Z-INDEX:%POS; " "}\n", id, - x, - y, - w, - h, - //-1, w+1, h+1, -1, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), z ); diff --git a/centrallix/htmlgen/htdrv_button.c b/centrallix/htmlgen/htdrv_button.c index 67439566e..1779b54be 100644 --- a/centrallix/htmlgen/htdrv_button.c +++ b/centrallix/htmlgen/htdrv_button.c @@ -150,7 +150,21 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if (!strcmp(type,"image") || !strcmp(type,"textoverimage")) { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); + htrAddStylesheetItem_va(s, + "\t#gb%POSpane { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z + ); htrAddScriptGlobal(s, "gb_cur_img", "null", 0); htrAddScriptGlobal(s, "gb_current", "null", 0); @@ -191,10 +205,24 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) } /* text over image */ if(!strcmp(type,"textoverimage")) - { - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+1); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h,fgcolor1,text); - } + { + htrAddStylesheetItem_va(s, + "\t#gb%POSpane2 { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:%POS; " + "TOP:%POS; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + 0, + 0, + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z+1 + ); + htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h,fgcolor1,text); + } else htrAddBodyItem_va(s,""); } @@ -226,11 +254,11 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if(s->Capabilities.Dom0NS) { /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:%STR; LEFT:-1; TOP:-1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_enabled?"inherit":"hidden",w-1,z+1); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_enabled?"hidden":"inherit",w-1,z+1); - htrAddStylesheetItem_va(s,"\t#gb%POStop { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",w,z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSbtm { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",w,z+2); + htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:"ht_flex_format"; TOP:"ht_flex_format"; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(x,tree->Parent->width,ht_get_fl_x(tree)),ht_flex(y,tree->Parent->height,ht_get_fl_y(tree)),ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); + htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:%STR; LEFT:-1; TOP:-1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_enabled?"inherit":"hidden",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+1); + htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_enabled?"hidden":"inherit",ht_flex(w-1,tree->Parent->width,ht_get_fl_w(tree)),z+1); + htrAddStylesheetItem_va(s,"\t#gb%POStop { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+2); + htrAddStylesheetItem_va(s,"\t#gb%POSbtm { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+2); htrAddStylesheetItem_va(s,"\t#gb%POSrgt { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); htrAddStylesheetItem_va(s,"\t#gb%POSlft { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); @@ -307,19 +335,59 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) { if(h >=0 ) { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx %INTpx %INTpx)}\n",id,x,y,w-1-2*box_offset,z,0,w-1-2*box_offset+2*clip_offset,h-1-2*box_offset+2*clip_offset,0); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2, #gb%POSpane3 { height: %POSpx;}\n",id,id,h-3); - htrAddStylesheetItem_va(s,"\t#gb%POSpane { height: %POSpx;}\n",id,h-1-2*box_offset); + htrAddStylesheetItem_va(s, + "\t#gb%POSpane { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "OVERFLOW:hidden; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "CLIP:rect(" + "0, " + ht_flex_format", " + ht_flex_format", " + "0" + "); " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(w-1-2*box_offset+2*clip_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h-1-2*box_offset+2*clip_offset, tree->Parent->height, ht_get_fl_h(tree)), + z + ); + htrAddStylesheetItem_va(s,"\t#gb%POSpane2, #gb%POSpane3 { height: "ht_flex_format";}\n",id,id,ht_flex(h-3,tree->Parent->height,ht_get_fl_h(tree))); + htrAddStylesheetItem_va(s,"\t#gb%POSpane { height: "ht_flex_format";}\n",id,ht_flex(h-1-2*box_offset,tree->Parent->height,ht_get_fl_h(tree))); } else { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx auto %INTpx)}\n",id,x,y,w-1-2*box_offset,z,0,w-1-2*box_offset+2*clip_offset,0); + htrAddStylesheetItem_va(s, + "\t#gb%POSpane { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "OVERFLOW:hidden; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "CLIP:rect(0, "ht_flex_format", auto, 0);" + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(w-1-2*box_offset+2*clip_offset, tree->Parent->width, ht_get_fl_w(tree)), + z + ); } htrAddStylesheetItem_va(s,"\t#gb%POSpane, #gb%POSpane2, #gb%POSpane3 { cursor:default; text-align: center; }\n",id,id,id); htrAddStylesheetItem_va(s,"\t#gb%POSpane { %STR border-width: 1px; border-style: solid; border-color: white gray gray white; }\n",id,bgstyle); /*htrAddStylesheetItem_va(s,"\t#gb%dpane { color: %s; }\n",id,fgcolor2);*/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:-1px; top: -1px; width:%POSpx; }\n",id,is_enabled?"inherit":"hidden",z+1,w-3); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:0px; top: 0px; width:%POSpx; }\n",id,is_enabled?"hidden":"inherit",z+1,w-3); + htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:-1px; top: -1px; width:"ht_flex_format"; }\n",id,is_enabled?"inherit":"hidden",z+1,ht_flex(w-3,tree->Parent->width,ht_get_fl_w(tree))); + htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:0px; top: 0px; width:"ht_flex_format"; }\n",id,is_enabled?"hidden":"inherit",z+1,ht_flex(w-3,tree->Parent->width,ht_get_fl_w(tree))); if(!strcmp(type,"text")) { diff --git a/centrallix/htmlgen/htdrv_editbox.c b/centrallix/htmlgen/htdrv_editbox.c index 0223038f9..7290692a7 100644 --- a/centrallix/htmlgen/htdrv_editbox.c +++ b/centrallix/htmlgen/htdrv_editbox.c @@ -149,8 +149,35 @@ htebRender(pHtSession s, pWgtrNode tree, int z) box_offset = 0; /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#eb%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; overflow:hidden; }\n",id,x,y,w-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#eb%POScon1 { VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; border:none; }\n",id,5,0,w-10,z+1); + htrAddStylesheetItem_va(s, + "\t#eb%POSbase { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "overflow:hidden; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w - 2 * box_offset, tree->Parent->width, ht_get_fl_w(tree)), + z + ); + htrAddStylesheetItem_va(s, + "\t#eb%POScon1 { " + "VISIBILITY:inherit; " + "LEFT:5px; " + "TOP:0px; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "border:none; " + "}\n", + id, + ht_flex(w - 10, tree->Parent->width, ht_get_fl_w(tree)), + z+1 + ); /** Write named global **/ htrAddWgtrObjLinkage_va(s, tree, "eb%POSbase",id); @@ -187,7 +214,12 @@ htebRender(pHtSession s, pWgtrNode tree, int z) else htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: gray white white gray; %STR }\n",id, main_bg); if (h >= 0) - htrAddStylesheetItem_va(s,"\t#eb%POSbase { height:%POSpx; }\n\t#eb%POScon1 { height:%POSpx; }\n", id, h-2*box_offset, id, h-2*box_offset-2); + htrAddStylesheetItem_va(s, + "\t#eb%POSbase { height:"ht_flex_format"; }\n" + "\t#eb%POScon1 { height:"ht_flex_format"; }\n", + id, ht_flex(h - 2 * box_offset, tree->Parent->height, ht_get_fl_h(tree)), + id, ht_flex(h - 2 * box_offset - 2, tree->Parent->height, ht_get_fl_h(tree)) + ); //htrAddBodyItem_va(s, "
 
\n", w-2, h-2); //htrAddBodyItem_va(s, "
\n",id); diff --git a/centrallix/htmlgen/htdrv_html.c b/centrallix/htmlgen/htdrv_html.c index 9b899802a..3e7238209 100644 --- a/centrallix/htmlgen/htdrv_html.c +++ b/centrallix/htmlgen/htdrv_html.c @@ -101,17 +101,59 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) { /** Only give x and y if supplied. **/ if (x < 0 || y < 0) - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z+1); - } + { + htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); + htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); + htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+1); + } else - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z+1); - } + { + htrAddStylesheetItem_va(s, + "\t#ht%POSpane { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z + ); + htrAddStylesheetItem_va(s, + "\t#ht%POSpane2 { " + "POSITION:absolute; " + "VISIBILITY:hidden; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z + ); + htrAddStylesheetItem_va(s, + "\t#ht%POSfader { " + "POSITION:absolute; " + "VISIBILITY:hidden; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z+1 + ); + } if (s->Capabilities.CSS1) { diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index 7ea3b9d47..b7195771b 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -145,7 +145,22 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) form[0]='\0'; /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#img%POS { left:%INTpx; top:%INTpx; width:%POSpx; height:%POSpx; z-index:%POS; text-align:center; }\n",id,x,y,w,h,z); + htrAddStylesheetItem_va(s, + "\t#img%POS { " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "text-align:center; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + z + ); /** Init image widget (?) **/ htrAddWgtrObjLinkage_va(s, tree, "img%POS",id); diff --git a/centrallix/htmlgen/htdrv_label.c b/centrallix/htmlgen/htdrv_label.c index d7140489f..27f53c9d2 100644 --- a/centrallix/htmlgen/htdrv_label.c +++ b/centrallix/htmlgen/htdrv_label.c @@ -197,17 +197,60 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) form[0]='\0'; /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#lbl%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]WIDTH:%POSpx; Z-INDEX:%POS; cursor:default; %[font-weight:bold; %]%[color:%STR&CSSVAL; %]%[font-size:%POSpx; %]text-align:%STR&CSSVAL; vertical-align:%STR&CSSVAL; %[white-space:nowrap; %]%[text-overflow:ellipsis; overflow:hidden; %]%[font-style:italic; %]}\n", - id,x,y, - !auto_height, h, - w,z, - is_bold, *fgcolor, fgcolor, font_size > 0, font_size, align, valign, - !allow_break, overflow_ellipsis, is_italic); + htrAddStylesheetItem_va(s, + "\t#lbl%POS { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "%[HEIGHT:"ht_flex_format"; %]" + "Z-INDEX:%POS; " + "cursor:default; " + "%[font-weight:bold; %]" + "%[color:%STR&CSSVAL; %]" + "%[font-size:%POSpx; %]" + "text-align:%STR&CSSVAL; " + "vertical-align:%STR&CSSVAL; " + "%[white-space:nowrap; %]" + "%[text-overflow:ellipsis; overflow:hidden; %]" + "%[font-style:italic; %]" + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + (!auto_height), ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + z, + (is_bold), + (*fgcolor), fgcolor, + (font_size > 0), font_size, + align, + valign, + (!allow_break), + (overflow_ellipsis), + (is_italic) + ); + if (is_link) htrAddStylesheetItem_va(s,"\t#lbl%POS:hover { %[color:%STR&CSSVAL; %]text-decoration:underline; cursor:pointer; }\n", id, *pfgcolor, pfgcolor); if (is_link && *cfgcolor) htrAddStylesheetItem_va(s,"\t#lbl%POS:active { color:%STR&CSSVAL; text-decoration:underline; cursor:pointer; }\n", id, cfgcolor); - htrAddStylesheetItem_va(s,"\t#lbl%POS p { text-align:%STR&CSSVAL; %[position:relative; top:50%%; transform:translateY(-50%%); %]padding:0px; margin:0px; border-spacing:0px; width:%POSpx; }\n", id, align, !strcmp(valign, "middle"), w); + + htrAddStylesheetItem_va(s, + "\t#lbl%POS p { " + "text-align:%STR&CSSVAL; " + "%[position:relative; top:50%%; transform:translateY(-50%%); %]" + "padding:0px; " + "margin:0px; " + "border-spacing:0px; " + "width:"ht_flex_format"; " + "}\n", + id, + align, + !strcmp(valign, "middle"), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)) + ); htrAddWgtrObjLinkage_va(s, tree, "lbl%POS",id); stylestr[0] = '\0'; diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index db1a6ec40..3ceafbd4d 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -118,9 +118,52 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ if (s->Capabilities.Dom0NS) { - htrAddStylesheetItem_va(s,"\t#sp%POSpane { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; clip:rect(0px,%POSpx,%POSpx,0px); Z-INDEX:%POS; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); - htrAddStylesheetItem_va(s,"\t#sp%POSarea { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:0px; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w-18,z+1); - htrAddStylesheetItem_va(s,"\t#sp%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:18px; WIDTH:18px; Z-INDEX:%POS; }\n",id,w-18,z+1); + htrAddStylesheetItem_va(s, + "\t#sp%POSpane { " + "POSITION:absolute; " + "VISIBILITY:%STR; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "HEIGHT:"ht_flex_format"; " + "clip:rect(0px, "ht_flex_format", "ht_flex_format", 0px); " + "Z-INDEX:%POS; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + z + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSarea { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:0px; " + "TOP:0px; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_w(tree)), + z + 1 + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSthum { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:18px; " + "WIDTH:18px; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + z + 1 + ); } /** Write globals for internal use **/ @@ -155,15 +198,98 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) //htrAddStylesheetItem_va(s,"\t#sp%dpane { POSITION:absolute; VISIBILITY:%s; LEFT:%dpx; TOP:%dpx; WIDTH:%dpx; HEIGHT:%dpx; clip:rect(0px,%dpx,%dpx,0px); Z-INDEX:%d; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); //htrAddStylesheetItem_va(s,"\t#sp%darea { HEIGHT: %dpx; WIDTH:%dpx; }\n",id, h, w-18); //htrAddStylesheetItem_va(s,"\t#sp%dthum { POSITION:absolute; VISIBILITY:inherit; LEFT:%dpx; TOP:18px; WIDTH:18px; Z-INDEX:%dpx; }\n",id,w-18,z+1); - htrAddBodyItem_va(s,"
\n",id,visible?"inherit":"hidden",x,y,w,h,w,h,z); + htrAddBodyItem_va(s, + "
\n", + id, + (visible) ? "inherit" : "hidden", + ht_flex(x, tree->Parent->width, ht_fl_x_compat), + ht_flex(y, tree->Parent->height, ht_fl_y_compat), + ht_flex(w, tree->Parent->width, tree->fl_width), + ht_flex(h, tree->Parent->height, tree->fl_height), + ht_flex(w, tree->Parent->width, tree->fl_width), ht_flex(h, tree->Parent->height, tree->fl_height), + z + ); htrAddBodyItem_va(s,"", id); htrAddBodyItem_va(s,"", id); htrAddBodyItem_va(s,"", id); - htrAddStylesheetItem_va(s,"\t#sp%POSup { POSITION: absolute; LEFT: %INTpx; TOP: 0px; }\n",id, w-18); - htrAddStylesheetItem_va(s,"\t#sp%POSbar { POSITION: absolute; LEFT: %INTpx; TOP: 18px; WIDTH: 18px; HEIGHT: %POSpx;}\n",id, w-18, h-36); - htrAddStylesheetItem_va(s,"\t#sp%POSdown { POSITION: absolute; LEFT: %INTpx; TOP: %INTpx; }\n",id, w-18, h-18); - htrAddBodyItem_va(s,"
\n", id,w-18,z+1); - htrAddBodyItem_va(s,"
",id,h,w-18,z+1); + htrAddStylesheetItem_va(s, + "\t#sp%POSup { " + "POSITION:absolute; " + "LEFT:"ht_flex_format"; " + "TOP:0px; " + "}\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)) + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSbar { " + "POSITION:absolute; " + "LEFT:"ht_flex_format"; " + "TOP:18px; " + "WIDTH:18px; " + "HEIGHT:"ht_flex_format"; " + "}\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(h - 36, tree->Parent->height, ht_get_fl_h(tree)) + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSdown { " + "POSITION:absolute; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "}\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(h - 18, tree->Parent->height, ht_get_fl_y(tree)) + ); + htrAddBodyItem_va(s, + "
" + "" + "
\n", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + z + 1 + ); + htrAddBodyItem_va(s, + "
", + id, + ht_flex(w - 18, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + z + 1 + ); } else { diff --git a/centrallix/htmlgen/htdrv_textbutton.c b/centrallix/htmlgen/htdrv_textbutton.c index 34575df52..1f6c1afef 100644 --- a/centrallix/htmlgen/htdrv_textbutton.c +++ b/centrallix/htmlgen/htdrv_textbutton.c @@ -206,10 +206,25 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); /** Initial CSS styles **/ - htrAddStylesheetItem_va(s,"\t#tb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; display:table; }\n", - id, - x, y, h>=0, h-1-2*box_offset, w-1-2*box_offset, z - ); + htrAddStylesheetItem_va(s, + "\t#tb%POSpane { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "%[HEIGHT:"ht_flex_format"; %]" + "Z-INDEX:%POS; " + "OVERFLOW:hidden; " + "display:table; " + "}\n", + id, + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), + (h>=0), ht_flex(h-1-2*box_offset, tree->Parent->height, ht_get_fl_h(tree)), + z + ); htrAddStylesheetItem_va(s, "\t#tb%POSpane .cell { height:100%%; width:100%%; vertical-align:middle; display:table-cell; padding:1px; font-weight:bold; cursor:default; text-align:%STR; border-width:1px; border-style:%STR&CSSVAL; border-color:%STR&CSSVAL; border-radius:%INTpx; color:%STR&CSSVAL; %[text-shadow:1px 1px %STR&CSSVAL; %]%STR }\n", /* clipping no longer needed: 0, w-1-2*box_offset+2*clip_offset, h-1-2*box_offset+2*clip_offset, 0, */ id, @@ -249,14 +264,23 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) //htrAddBodyItem(s, "
"); /** We need two DIVs here because of a long-outstanding Firefox bug :( **/ - htrAddBodyItem_va(s,"
%[
%]%[%]%STR&HTE%[%]%[
%]
", - id, - image[0] && !strcmp(image_position, "top"), image, - image[0] && !strcmp(image_position, "left"), image, - text, - image[0] && !strcmp(image_position, "right"), image, - image[0] && !strcmp(image_position, "bottom"), image - ); + htrAddBodyItem_va(s, + "
" + "
" + "%[
%]" + "%[%]" + "%STR&HTE" + "%[%]" + "%[
%]" + "
" + "
", + id, + (image[0] && !strcmp(image_position, "top")), image, + (image[0] && !strcmp(image_position, "left")), image, + text, + (image[0] && !strcmp(image_position, "right")), image, + (image[0] && !strcmp(image_position, "bottom")), image + ); /** Script initialization call. **/ //htrAddScriptInit_va(s, " tb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), span:document.getElementById(\"tb%POSspan\"), ena:%INT, c1:\"%STR&JSSTR\", c2:\"%STR&JSSTR\", dc1:\"%STR&JSSTR\", top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR'});\n", diff --git a/centrallix/htmlgen/htdrv_treeview.c b/centrallix/htmlgen/htdrv_treeview.c index 413a2123c..106b49718 100644 --- a/centrallix/htmlgen/htdrv_treeview.c +++ b/centrallix/htmlgen/htdrv_treeview.c @@ -172,7 +172,22 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ if (s->Capabilities.Dom0NS) { - htrAddStylesheetItem_va(s,"\t#tv%POSroot { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,show_root?"inherit":"hidden",x,y,w,z); + htrAddStylesheetItem_va(s, + "\t#tv%POSroot { " + "POSITION:absolute; " + "VISIBILITY:%STR; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + (show_root) ? "inherit" : "hidden", + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z + ); } htrAddStylesheetItem_va(s,"\t#tv%POSload { POSITION:absolute; VISIBILITY:hidden; OVERFLOW:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; clip:rect(0px,0px,0px,0px); Z-INDEX:0; }\n",id); htrAddStylesheetItem_va(s,"\tdiv.tv%POS a { %[color:%STR&CSSVAL;%] }\n", id, *fgcolor, fgcolor); @@ -205,7 +220,31 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) } else { - htrAddBodyItem_va(s, "
 %STR&HTE
\n",id,id,show_root?"inherit":"hidden",x,y,w,z,(*icon)?icon:"/sys/images/ico02b.gif", src); + htrAddBodyItem_va(s, + "
" + "" + " %STR&HTE" + "
\n", + id, /** Class **/ + id, /** ID **/ + (show_root) ? "inherit" : "hidden", + ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), + ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + z, + (*icon) ? icon : "/sys/images/ico02b.gif", src + ); htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "tv%POSload", id, NULL, ""); /*htrAddBodyItem_va(s, "
\n",id);*/ } From dcbed96d77eb2974478c6f76eae6f0e8488f05e1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Thu, 3 Jul 2025 15:37:40 -0600 Subject: [PATCH 016/101] Build testing apps. --- centrallix-os/samples/autoscale_test.app | 71 +++++++++++++++++++++++ centrallix-os/samples/autoscale_test2.app | 19 ++++++ 2 files changed, 90 insertions(+) create mode 100644 centrallix-os/samples/autoscale_test.app create mode 100644 centrallix-os/samples/autoscale_test2.app diff --git a/centrallix-os/samples/autoscale_test.app b/centrallix-os/samples/autoscale_test.app new file mode 100644 index 000000000..bcaace5c6 --- /dev/null +++ b/centrallix-os/samples/autoscale_test.app @@ -0,0 +1,71 @@ +$Version=2$ +MyPage "widget/page" + { + title = "Responsive Testing Page"; + bgcolor = "black"; + textcolor = "#00f8ff"; + width = 1000; + height = 1000; + + auto "widget/hbox" + { + x=100; y=50; width=900; height=750; + spacing=20; row_height=300; + fl_width=100; fl_height=100; + + pane0 "widget/pane" { fl_width=100; fl_height=100; width=190; height=180; bgcolor = "#9cf"; } // x=100; y=50; + pane1 "widget/pane" { fl_width=100; fl_height=100; width=130; height=180; bgcolor = "#ccc"; } // x=305; y=50; + pane2 "widget/pane" { fl_width=100; fl_height=100; width=80; height=320; bgcolor = "#f99"; } // x=455; y=50; + pane3 "widget/pane" { fl_width=100; fl_height=100; width=150; height=140; bgcolor = "#9f9"; } // x=555; y=50; + pane4 "widget/pane" { fl_width=100; fl_height=100; width=90; height=240; bgcolor = "#99f"; } // x=725; y=50; + pane5 "widget/pane" { fl_width=100; fl_height=100; width=230; height=100; bgcolor = "#ff9"; } // x=80; y=390; + pane6 "widget/pane" { fl_width=100; fl_height=100; width=80; height=200; bgcolor = "#f9f"; } // x=325; y=390; + pane7 "widget/pane" { fl_width=100; fl_height=100; width=170; height=220; bgcolor = "#9ff"; } // x=425; y=390; + pane8 "widget/pane" { fl_width=100; fl_height=100; width=110; height=200; bgcolor = "#fc9"; } // x=615; y=390; + pane9 "widget/pane" { fl_width=100; fl_height=100; width=130; height=120; bgcolor = "#cf9"; } // x=745; y=390; + } + + // pane0 "widget/pane" { x=100; y=50; width=190; height=180; bgcolor = "#9cf"; } + // pane1 "widget/pane" { x=305; y=50; width=130; height=180; bgcolor = "#ccc"; } + // pane2 "widget/pane" { x=455; y=50; width=80; height=320; bgcolor = "#f99"; } + // pane3 "widget/pane" { x=555; y=50; width=150; height=140; bgcolor = "#9f9"; } + // pane4 "widget/pane" { x=725; y=50; width= 90; height=240; bgcolor = "#99f"; } + // pane5 "widget/pane" { x=80; y=390; width=230; height=100; bgcolor = "#ff9"; } + // pane6 "widget/pane" { x=325; y=390; width=80; height=200; bgcolor = "#f9f"; } + // pane7 "widget/pane" { x=425; y=390; width=170; height=220; bgcolor = "#9ff"; } + // pane8 "widget/pane" { x=615; y=390; width=110; height=200; bgcolor = "#fc9"; } + // pane9 "widget/pane" { x=745; y=390; width=130; height=120; bgcolor = "#cf9"; } + + paneA "widget/pane" { x=40; y=680; width=890; height=220; bgcolor = "#620"; } + + // Outline the visible area. + top_left0 "widget/pane" { x=0; y=0; width=10; height=10; bgcolor = "#f00"; } + top_right0 "widget/pane" { x=990; y=0; width=10; height=10; bgcolor = "#ff0"; } + bottom_left0 "widget/pane" { x=0; y=990; width=10; height=10; bgcolor = "#0f0"; } + bottom_right0 "widget/pane" { x=990; y=990; width=10; height=10; bgcolor = "#00f"; } + + // Advance markers. + top_left1 "widget/pane" { x=100; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#a00"; } + top_right1 "widget/pane" { x=890; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#aa0"; } + bottom_left1 "widget/pane" { x=100; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#0a0"; } + bottom_right1 "widget/pane" { x=890; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#00a"; } + + // Interior markers. + top_left2 "widget/pane" { x=250; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#700"; } + top_right2 "widget/pane" { x=740; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#770"; } + bottom_left2 "widget/pane" { x=250; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#070"; } + bottom_right2 "widget/pane" { x=740; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#007"; } + + // Deep interior markers. + top_left3 "widget/pane" { x=400; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#500"; } + top_right3 "widget/pane" { x=590; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#550"; } + bottom_left3 "widget/pane" { x=400; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#050"; } + bottom_right3 "widget/pane" { x=590; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#005"; } + + // Center marker. + center "widget/pane" + { + x=450; y=450; width=100; height=100; bgcolor = "orange"; + centerer "widget/pane" { x=25; y=25; width=50; height=50; bgcolor = "purple"; } // Debug + } + } \ No newline at end of file diff --git a/centrallix-os/samples/autoscale_test2.app b/centrallix-os/samples/autoscale_test2.app new file mode 100644 index 000000000..10b7a4cff --- /dev/null +++ b/centrallix-os/samples/autoscale_test2.app @@ -0,0 +1,19 @@ +$Version=2$ +MyPage "widget/page" + { + title = "Flex Testing Page"; + bgcolor = "black"; + textcolor = "#00f8ff"; + width = 1000; + height = 1000; + + box "widget/pane" + { + x=25; y=25; width=975; height=975; bgcolor = "#111"; + + // Note: fl_x and fl_y seem to be ignored. + standard "widget/pane" { x=100; y=100; width=100; height=100; fl_x=100; fl_y=100; fl_width=100; fl_height=100; bgcolor = "orange"; } + big "widget/pane" { x=250; y=250; width=200; height=200; fl_x=10; fl_y=50; fl_width=10; fl_height=10; bgcolor = "purple"; } + double "widget/pane" { x=500; y=500; width=200; height=200; fl_x=50; fl_y=10; fl_width=50; fl_height=50; bgcolor = "green"; } + } + } \ No newline at end of file From 4c0bad951d0f0be0f952df87cbdaddb3c7221fbc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 23 Jul 2025 14:21:00 -0600 Subject: [PATCH 017/101] Sample pages appear to work now. --- centrallix-os/samples/index.app | 2 +- centrallix-os/sys/js/ht_geom_dom1html.js | 42 ++++++++++++++++++- centrallix-os/sys/js/htdrv_html.js | 15 ++++++- centrallix-os/sys/js/htdrv_scrollpane.js | 44 ++++++++++++++------ centrallix-os/sys/js/htdrv_textbutton.js | 2 - centrallix/htmlgen/ht_render.c | 37 +++++++++++++++-- centrallix/htmlgen/htdrv_autolayout.c | 8 ++-- centrallix/htmlgen/htdrv_editbox.c | 12 +++--- centrallix/htmlgen/htdrv_html.c | 24 +++++------ centrallix/htmlgen/htdrv_image.c | 8 ++-- centrallix/htmlgen/htdrv_label.c | 10 ++--- centrallix/htmlgen/htdrv_pane.c | 8 ++-- centrallix/htmlgen/htdrv_scrollpane.c | 42 +++++++++---------- centrallix/htmlgen/htdrv_textbutton.c | 50 +++++++++++++++++------ centrallix/htmlgen/htdrv_treeview.c | 14 +++---- centrallix/include/ht_render.h | 52 ++++++++++++++++-------- centrallix/wgtr/apos.c | 24 ++++++----- 17 files changed, 268 insertions(+), 126 deletions(-) diff --git a/centrallix-os/samples/index.app b/centrallix-os/samples/index.app index a7765e905..492ab04af 100644 --- a/centrallix-os/samples/index.app +++ b/centrallix-os/samples/index.app @@ -74,7 +74,7 @@ index "widget/page" info_html "widget/html" { - x=1;y=0;width = 356; + x=10;y=0;width = 356; mode=dynamic; source="/samples/welcome.html"; } diff --git a/centrallix-os/sys/js/ht_geom_dom1html.js b/centrallix-os/sys/js/ht_geom_dom1html.js index b8c17ab7d..f137d5dba 100644 --- a/centrallix-os/sys/js/ht_geom_dom1html.js +++ b/centrallix-os/sys/js/ht_geom_dom1html.js @@ -11,6 +11,35 @@ // Cross browser Geometry DOM1HTML +/*** Experimental system for turning off clipping CSS. + *** The clip values are still stored and can be queried + *** for legacy compatibility, but they will not output + *** any clip rectangles or clip paths in the CSS or HTML. + ***/ +/** Ensure clipping is disabled for a layer / HTML node. **/ +function disableClippingCSS(l) { + console.log(`Turning off clipping for ${l.clip.obj.id}.`); + l.clip.noclip = true; + updateClippingCSS(l); +} +/** Ensure clipping is enabled for a layer / HTML node. **/ +function enableClippingCSS(l) { + l.clip.noclip = false; + updateClippingCSS(l); +} +/** Update clipping without changing any specific values. **/ +function updateClippingCSS(l) { + setClipTop(l, getClipTop(l)); +} + +/** Debug function for finding clipped dom nodes. **/ +function getClipped() { + return Array + .from(Window.clipped) + .filter(id=>id) + .map(id=>document.getElementById(id)); +} + // Clip Width function getClipWidth(l) { @@ -321,6 +350,7 @@ function getHeight(l) // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. +Window.clipped = new Set(); // Debug set for saving clipped dom nodes. function ClipObject_SetAll(top,right,bottom,left) { var str = "rect(" @@ -329,7 +359,17 @@ function ClipObject_SetAll(top,right,bottom,left) + bottom + "px, " + left + "px)"; this.arr = {1:top,2:right,3:bottom,4:left}; - this.obj.style.setProperty('clip',str,""); + if (this.noclip) + { + Window.clipped.delete(this.obj.id); // debug + this.obj.style.setProperty('clip', ""); + } + else + { + Window.clipped.add(this.obj.id); // debug + this.obj.style.setProperty('clip', str, ""); + } + } var ClipRegexp = /rect\((.*), (.*), (.*), (.*)\)/; diff --git a/centrallix-os/sys/js/htdrv_html.js b/centrallix-os/sys/js/htdrv_html.js index b40264bec..3289d0b61 100644 --- a/centrallix-os/sys/js/htdrv_html.js +++ b/centrallix-os/sys/js/htdrv_html.js @@ -240,7 +240,10 @@ function ht_init(param) { setClipHeight(l, getdocHeight(l)); } - pg_set_style(l, 'height', getdocHeight(l)); + /*** This break's responsive design, and it looks like it's + *** put in for compatibility and is no longer necessary. + ***/ + // pg_set_style(l, 'height', getdocHeight(l)); if (param.width != -1) { setClipWidth(l, param.width); @@ -250,7 +253,15 @@ function ht_init(param) { setClipWidth(l, getdocWidth(l)); } - pg_set_style(l, 'width', getdocWidth(l)); + /*** This break's responsive design, and it looks like it's + *** put in for compatibility and is no longer necessary. + ***/ + // pg_set_style(l, 'width', getdocWidth(l)); + + /** Clipping breaks responsive pages and is not required in modern browers. **/ + disableClippingCSS(l); + disableClippingCSS(l2); + if (source.substr(0,5) == 'http:') { //pg_serialized_load(l, source, ht_reloaded); diff --git a/centrallix-os/sys/js/htdrv_scrollpane.js b/centrallix-os/sys/js/htdrv_scrollpane.js index f57d1313f..ccbf0dba5 100644 --- a/centrallix-os/sys/js/htdrv_scrollpane.js +++ b/centrallix-os/sys/js/htdrv_scrollpane.js @@ -58,6 +58,7 @@ function sp_init(param) images[0].area=alayer; images[0].pane=l; setClipWidth(alayer, getClipWidth(l)-18); + disableClippingCSS(alayer); // Clipping breaks responsive pages and is not required in modern browers. alayer.maxwidth=getClipWidth(alayer); alayer.minwidth=getClipWidth(alayer); tlayer.nofocus = true; @@ -96,10 +97,27 @@ function sp_init(param) } } +/** @returns The height of the scrollpane content (including content outside the visibile area). **/ +// Replaces getClipHeight(area) + getClipTop(area) +function sp_get_content_height(area) { + return getClipHeight(area) + getClipTop(area); +} + +/** @returns The height of visible area in the scrollpane. **/ +// Replaces getClipHeight(pane) +function sp_get_total_height(pane) { + return parseInt(window.getComputedStyle(pane).height); +} + +/** @returns The height of visible area in the scroll bar. **/ +function sp_get_sb_height(pane) { + return sp_get_total_height(pane) - (3*18); +} + function sp_action_scrollto(aparam) { - var h = getClipHeight(this.area)+getClipTop(this.area); // height of content - var ch = getClipHeight(this); + var h = sp_get_content_height(this.area); // height of content + var ch = sp_get_total_height(this); var d = h-ch; // height of non-visible content (max scrollable distance) if (d < 0) d=0; if (typeof aparam.Percent != 'undefined') @@ -135,8 +153,10 @@ function sp_WatchHeight(property, oldvalue, newvalue) // make sure region not offscreen now newvalue += getClipTop(this.pane.area); - if (getRelativeY(this.pane.area) + newvalue < getClipHeight(this.pane)) setRelativeY(this.pane.area, getClipHeight(this.pane) - newvalue); - if (newvalue < getClipHeight(this.pane)) setRelativeY(this.pane.area, 0); + if (getRelativeY(this.pane.area) + newvalue < sp_get_total_height(this.pane)) { + setRelativeY(this.pane.area, sp_get_total_height(this.pane) - newvalue); + } + if (newvalue < sp_get_total_height(this.pane)) setRelativeY(this.pane.area, 0); this.pane.UpdateThumb(newvalue); newvalue -= getClipTop(this.pane.area); this.bottom = this.top + newvalue; /* ns seems to unlink bottom = top + height if you modify clip obj */ @@ -148,10 +168,10 @@ function sp_UpdateThumb(h) /** 'this' is a spXpane **/ if(!h) { /** if h is supplied, it is the soon-to-be clip.height of the spXarea **/ - h=getClipHeight(this.area)+getClipTop(this.area); // height of content + h=sp_get_content_height(this.area); // height of content } - var d=h-getClipHeight(this); // height of non-visible content (max scrollable distance) - var v=getClipHeight(this)-(3*18); + var d=h-sp_get_total_height(this); // height of non-visible content (max scrollable distance) + var v=sp_get_sb_height(this); if(d<=0) setRelativeY(this.thum, 18); else @@ -167,8 +187,8 @@ function do_mv() { return; } - var h=getClipHeight(ti.area)+getClipTop(ti.area); // height of content - var d=h-getClipHeight(ti.pane); // height of non-visible content (max scrollable distance) + var h=sp_get_content_height(ti.area); // height of content + var d=h-sp_get_total_height(ti.pane); // height of non-visible content (max scrollable distance) var incr=sp_mv_incr; if(d<0) incr=0; @@ -253,13 +273,13 @@ function sp_mousemove(e) var ti=sp_target_img; if (ti != null && ti.kind=='sp' && ti.name=='t') { - var v=getClipHeight(ti.pane)-(3*18); + var v=sp_get_sb_height(ti.pane); var new_y=sp_thum_y + (e.pageY-sp_click_y); if (new_y > getPageY(ti.pane)+18+v) new_y=getPageY(ti.pane)+18+v; if (new_y < getPageY(ti.pane)+18) new_y=getPageY(ti.pane)+18; setPageY(ti.thum,new_y); - var h=getClipHeight(ti.area)+getClipTop(ti.area); - var d=h-getClipHeight(ti.pane); + var h=sp_get_content_height(ti.area); + var d=h-sp_get_total_height(ti.pane); if (d<0) d=0; var yincr = (((getRelativeY(ti.thum)-18)/v)*-d) - getRelativeY(ti.area); moveBy(ti.area, 0, yincr); diff --git a/centrallix-os/sys/js/htdrv_textbutton.js b/centrallix-os/sys/js/htdrv_textbutton.js index 77b968e75..d053453b1 100644 --- a/centrallix-os/sys/js/htdrv_textbutton.js +++ b/centrallix-os/sys/js/htdrv_textbutton.js @@ -226,7 +226,6 @@ function tb_setmode(layer,mode) break; case 1: /* point, but no click */ - moveTo(layer,layer.orig_x,layer.orig_y); $(layer).find(".cell").css({'border-style':wgtrGetServerProperty(layer, 'border_style', 'outset'), 'border-color':wgtrGetServerProperty(layer, 'border_color', '#c0c0c0')}); /*if(cx__capabilities.Dom2CSS) { @@ -259,7 +258,6 @@ function tb_setmode(layer,mode) break; case 2: /* point and click */ - moveTo(layer,layer.orig_x+1,layer.orig_y+1); var bstyle = wgtrGetServerProperty(layer, 'border_style', 'outset'); if (bstyle == 'outset') bstyle = 'inset'; diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 0a6844768..c98f81b14 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -2720,10 +2720,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - ht_flex(x, node->Parent->width, ht_get_fl_x(node)), - ht_flex(y, node->Parent->height, ht_get_fl_y(node)), - (w > 0), ht_flex(w, node->Parent->width, ht_get_fl_w(node)), - (h > 0), ht_flex(h, node->Parent->height, ht_get_fl_h(node)), + ht_flex(x, ht_get_total_w(node), ht_get_fl_x(node)), + ht_flex(y, ht_get_total_h(node), ht_get_fl_y(node)), + (w > 0), ht_flex(w, ht_get_total_w(node), ht_get_fl_w(node)), + (h > 0), ht_flex(h, ht_get_total_h(node), ht_get_fl_h(node)), (z > 0), z, (*textcolor), textcolor, (!strcmp(style, "bold")), @@ -2746,3 +2746,32 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y } +int +ht_get_total_w__INTERNAL(pWgtrNode widget) { + pWgtrNode parent = widget->Parent; + int parentWidth = parent->width; +// printf( +// "Getting total width available to '%s' (%s), child of '%s' (%s) - %dpx\n", +// widget->Name, widget->Type, parent->Name, parent->Type, parentWidth +// ); + if (parentWidth >= 0) { + int offset = parent->left + parent->right, ret = parentWidth - offset; + // printf("Returning %d-%d=%d\n", parentWidth, offset, ret); + return ret; + } else return ht_get_total_w(parent); // Tail-recursion +} + +int +ht_get_total_h__INTERNAL(pWgtrNode widget) { + pWgtrNode parent = widget->Parent; + int parentHeight = parent->height; +// printf( +// "Getting total height available to '%s' (%s), child of '%s' (%s) - %dpx\n", +// widget->Name, widget->Type, parent->Name, parent->Type, parentHeight +// ); + if (parentHeight >= 0) { + int offset = parent->top + parent->bottom, ret = parentHeight - offset; + // printf("Returning %d-%d=%d\n", parentHeight, offset, ret); + return ret; + } else return ht_get_total_h(parent); // Tail-recursion +} diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index 66b7f83e0..9cc7eb7c4 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -111,10 +111,10 @@ htalRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), z ); diff --git a/centrallix/htmlgen/htdrv_editbox.c b/centrallix/htmlgen/htdrv_editbox.c index 7290692a7..ef40c8d8a 100644 --- a/centrallix/htmlgen/htdrv_editbox.c +++ b/centrallix/htmlgen/htdrv_editbox.c @@ -160,9 +160,9 @@ htebRender(pHtSession s, pWgtrNode tree, int z) "overflow:hidden; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w - 2 * box_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w - 2 * box_offset, ht_get_total_w(tree), ht_get_fl_w(tree)), z ); htrAddStylesheetItem_va(s, @@ -175,7 +175,7 @@ htebRender(pHtSession s, pWgtrNode tree, int z) "border:none; " "}\n", id, - ht_flex(w - 10, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(w - 10, ht_get_total_w(tree), ht_get_fl_w(tree)), z+1 ); @@ -217,8 +217,8 @@ htebRender(pHtSession s, pWgtrNode tree, int z) htrAddStylesheetItem_va(s, "\t#eb%POSbase { height:"ht_flex_format"; }\n" "\t#eb%POScon1 { height:"ht_flex_format"; }\n", - id, ht_flex(h - 2 * box_offset, tree->Parent->height, ht_get_fl_h(tree)), - id, ht_flex(h - 2 * box_offset - 2, tree->Parent->height, ht_get_fl_h(tree)) + id, ht_flex(h - 2 * box_offset, ht_get_total_h(tree), ht_get_fl_h(tree)), + id, ht_flex(h - 2 * box_offset - 2, ht_get_total_h(tree), ht_get_fl_h(tree)) ); //htrAddBodyItem_va(s, "
 
\n", w-2, h-2); diff --git a/centrallix/htmlgen/htdrv_html.c b/centrallix/htmlgen/htdrv_html.c index 3e7238209..0b41d7b35 100644 --- a/centrallix/htmlgen/htdrv_html.c +++ b/centrallix/htmlgen/htdrv_html.c @@ -102,9 +102,9 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) /** Only give x and y if supplied. **/ if (x < 0 || y < 0) { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+1); + htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z); + htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z); + htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z+1); } else { @@ -118,9 +118,9 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), z ); htrAddStylesheetItem_va(s, @@ -133,9 +133,9 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), z ); htrAddStylesheetItem_va(s, @@ -148,9 +148,9 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), z+1 ); } diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index b7195771b..7ad156f71 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -155,10 +155,10 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) "text-align:center; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), z ); diff --git a/centrallix/htmlgen/htdrv_label.c b/centrallix/htmlgen/htdrv_label.c index 27f53c9d2..ea315a9a5 100644 --- a/centrallix/htmlgen/htdrv_label.c +++ b/centrallix/htmlgen/htdrv_label.c @@ -217,10 +217,10 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) "%[font-style:italic; %]" "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), - (!auto_height), ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + (!auto_height), ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), z, (is_bold), (*fgcolor), fgcolor, @@ -249,7 +249,7 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) id, align, !strcmp(valign, "middle"), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)) + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)) ); htrAddWgtrObjLinkage_va(s, tree, "lbl%POS",id); diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 9d0f23416..0caf96258 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -197,10 +197,10 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "%STR" "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w + offset, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h + offset, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w + offset, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h + offset, ht_get_total_h(tree), ht_get_fl_h(tree)), z, border_radius, main_bg diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index 3ceafbd4d..e5c061d16 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -126,16 +126,15 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "TOP:"ht_flex_format"; " "WIDTH:"ht_flex_format"; " "HEIGHT:"ht_flex_format"; " - "clip:rect(0px, "ht_flex_format", "ht_flex_format", 0px); " + "OVERFLOW: clip; " "Z-INDEX:%POS; " "}\n", id, (visible) ? "inherit" : "hidden", - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), z ); htrAddStylesheetItem_va(s, @@ -148,7 +147,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(w - 18, ht_get_total_w(tree), 1.0), z + 1 ); htrAddStylesheetItem_va(s, @@ -161,7 +160,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(w - 18, tree->width, 1.0), z + 1 ); } @@ -207,17 +206,16 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "TOP:"ht_flex_format"; " "WIDTH:"ht_flex_format"; " "HEIGHT:"ht_flex_format"; " - "clip:rect(0, "ht_flex_format", "ht_flex_format", 0); " + "OVERFLOW: clip; " "Z-INDEX:%POS; " "\"" ">\n", id, (visible) ? "inherit" : "hidden", - ht_flex(x, tree->Parent->width, ht_fl_x_compat), - ht_flex(y, tree->Parent->height, ht_fl_y_compat), - ht_flex(w, tree->Parent->width, tree->fl_width), - ht_flex(h, tree->Parent->height, tree->fl_height), - ht_flex(w, tree->Parent->width, tree->fl_width), ht_flex(h, tree->Parent->height, tree->fl_height), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), z ); htrAddBodyItem_va(s,"", id); @@ -230,7 +228,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "TOP:0px; " "}\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)) + ht_flex(w - 18, tree->width, 1.0) ); htrAddStylesheetItem_va(s, "\t#sp%POSbar { " @@ -241,8 +239,8 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "HEIGHT:"ht_flex_format"; " "}\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(h - 36, tree->Parent->height, ht_get_fl_h(tree)) + ht_flex(w - 18, tree->width, 1.0), + ht_flex(h - 36, tree->height, 1.0) ); htrAddStylesheetItem_va(s, "\t#sp%POSdown { " @@ -251,8 +249,8 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "TOP:"ht_flex_format"; " "}\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(h - 18, tree->Parent->height, ht_get_fl_y(tree)) + ht_flex(w - 18, tree->width, 1.0), + ht_flex(h - 18, tree->height, 1.0) ); htrAddBodyItem_va(s, "
" "
\n", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_x(tree)), + ht_flex(w - 18, tree->width, 1.0), z + 1 ); htrAddBodyItem_va(s, @@ -286,8 +284,8 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "\"" ">", id, - ht_flex(w - 18, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(w - 18, ht_get_total_w(tree), 1.0), + ht_flex(h, ht_get_total_h(tree), 1.0), z + 1 ); } diff --git a/centrallix/htmlgen/htdrv_textbutton.c b/centrallix/htmlgen/htdrv_textbutton.c index 1f6c1afef..92c07357b 100644 --- a/centrallix/htmlgen/htdrv_textbutton.c +++ b/centrallix/htmlgen/htdrv_textbutton.c @@ -219,20 +219,46 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) "display:table; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), - (h>=0), ht_flex(h-1-2*box_offset, tree->Parent->height, ht_get_fl_h(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w-1-2*box_offset, ht_get_total_w(tree), ht_get_fl_w(tree)), + (h>=0), ht_flex(h-1-2*box_offset, ht_get_total_h(tree), ht_get_fl_h(tree)), z ); - htrAddStylesheetItem_va(s, "\t#tb%POSpane .cell { height:100%%; width:100%%; vertical-align:middle; display:table-cell; padding:1px; font-weight:bold; cursor:default; text-align:%STR; border-width:1px; border-style:%STR&CSSVAL; border-color:%STR&CSSVAL; border-radius:%INTpx; color:%STR&CSSVAL; %[text-shadow:1px 1px %STR&CSSVAL; %]%STR }\n", - /* clipping no longer needed: 0, w-1-2*box_offset+2*clip_offset, h-1-2*box_offset+2*clip_offset, 0, */ - id, - h_align, - border_style, border_color, border_radius, - is_enabled?fgcolor1:disable_color, is_enabled, fgcolor2, - bgstyle - ); + // CSS button click animation (replaces manual the JS implementation). + if (is_enabled) { + htrAddStylesheetItem_va(s, + "\t#tb%POSpane:active { transform: translate(1px, 1px); }\n", + id + ); + } + htrAddStylesheetItem_va(s, + "\t#tb%POSpane .cell { " + "height:100%%; " + "width:100%%; " + "vertical-align:middle; " + "display:table-cell; " + "padding:1px; " + "font-weight:bold; " + "cursor:default; " + "text-align:%STR; " + "border-width:1px; " + "border-style:%STR&CSSVAL; " + "border-color:%STR&CSSVAL; " + "border-radius:%INTpx; " + "color:%STR&CSSVAL; " + "%[text-shadow:1px 1px %STR&CSSVAL; %]" + "%STR " + "}\n", + id, + h_align, + border_style, + border_color, + border_radius, + (is_enabled) ? fgcolor1 : disable_color, + (is_enabled), fgcolor2, + bgstyle + ); /** CSS for image on the button **/ if (image[0] && (image_width || image_height || image_margin)) diff --git a/centrallix/htmlgen/htdrv_treeview.c b/centrallix/htmlgen/htdrv_treeview.c index 106b49718..05a2bae8f 100644 --- a/centrallix/htmlgen/htdrv_treeview.c +++ b/centrallix/htmlgen/htdrv_treeview.c @@ -183,13 +183,13 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) "}\n", id, (show_root) ? "inherit" : "hidden", - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), z ); } - htrAddStylesheetItem_va(s,"\t#tv%POSload { POSITION:absolute; VISIBILITY:hidden; OVERFLOW:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; clip:rect(0px,0px,0px,0px); Z-INDEX:0; }\n",id); + htrAddStylesheetItem_va(s,"\t#tv%POSload { POSITION:absolute; VISIBILITY:hidden; OVERFLOW:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; clip-path:inset(0px 0px 0px 0px); Z-INDEX:0; }\n",id); htrAddStylesheetItem_va(s,"\tdiv.tv%POS a { %[color:%STR&CSSVAL;%] }\n", id, *fgcolor, fgcolor); htrAddStylesheetItem_va(s,"\tdiv.tv%POSh a { %[color:%STR&CSSVAL;%] }\n", id, *hfgcolor, hfgcolor); @@ -239,9 +239,9 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) id, /** Class **/ id, /** ID **/ (show_root) ? "inherit" : "hidden", - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), z, (*icon) ? icon : "/sys/images/ico02b.gif", src ); diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index d078ba1e0..b725eb9d4 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -366,17 +366,25 @@ int htruleRegister(char* ruletype, ...); /** Define macros for implementing responsive dimensions. **/ /** ===================================================== **/ -/*** The widget fl_x and fl_y fields are never used in the generated layout. - *** Thus, the CSS I created to add responsiveness uses the values below - *** instead of the provided value to preserve backwards compatibility. +/*** Brief explanation of the responsiveness formula. + *** + *** Responsive dimensions in widgets use the following formula: + *** Original px + (100% - Total px) * Flex + *** Where "Original" is the original size of the object in an adaptive layout, + *** "Total" is the total size of the widget's parent container, and + *** "Flex" is the widget flexibility (where all flexibilities add to 1). + *** All, with respect to the given dimension. + *** + *** The intuition behind this formula is that (100% - Total px) is 0px + *** if the parent container is the size intended by the adaptive design. + *** However, if the user resizes the window, (100% - Total px) is the + *** difference between the size in the adaptive design and the current + *** size, so the widget changes size with respect to that difference. ***/ -/** @brief widget->fl_x is never used. Use this instead for compatibility. **/ -#define ht_fl_x_compat 1.0 -/** @brief widget->fl_y is never used. Use this instead for compatibility. **/ -#define ht_fl_y_compat 1.0 /** @brief The qprintf format to specify a responsive dimension. **/ -#define ht_flex_format "calc(%INTpx + (%DBL%% - %INTpx) * %DBL)" +#define ht_flex_format "calc(%INTpx + (100%% - %INTpx) * %DBL)" + /*** @brief The function which generates the values that should be passed to *** qprintf in order to satisfy an ht_flex_format. *** @@ -386,29 +394,44 @@ int htruleRegister(char* ruletype, ...); *** to generate this with an ht_get_fl function call. *** @returns Several values to serve as parameters for a qprintf call. ***/ -#define ht_flex(size, total, flex) (size), (double)(size) / (total) * 100.0, (size), (flex) +#define ht_flex(size, total, flex) (size), (total), (flex) + /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the x direction. ***/ -#define ht_get_fl_x(widget) (ht_fl_x_compat) +#define ht_get_fl_x(widget) ((widget)->xAdjWeight) + /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the y direction. ***/ -#define ht_get_fl_y(widget) (ht_fl_y_compat) +#define ht_get_fl_y(widget) ((widget)->yAdjWeight) + /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the width direction. ***/ #define ht_get_fl_w(widget) ((widget)->wAdjWeight) + /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the height direction. ***/ #define ht_get_fl_h(widget) ((widget)->hAdjWeight) + +int ht_get_total_w__INTERNAL(pWgtrNode widget); +int ht_get_total_h__INTERNAL(pWgtrNode widget); + +#define ht_get_total_w(widget) ht_get_total_w__INTERNAL(widget) +#define ht_get_total_h(widget) ht_get_total_h__INTERNAL(widget) + +// #define ht_get_total_w(widget) ((widget)->Parent->width - (widget)->Parent->left - (widget)->Parent->right) +// #define ht_get_total_h(widget) ((widget)->Parent->height - (widget)->Parent->top - (widget)->Parent->bottom) + /*** @brief A shortcut function to get the flexibility when writing the *** LEFT CSS attribute. *** @param widget The widget to be queried. *** @returns The flexibility of the widget in the left direction. ***/ #define ht_get_fl_l ht_get_fl_x + /*** @brief A shortcut function to get the flexibility when writing the *** TOP CSS attribute. *** @param widget The widget to be queried. @@ -416,12 +439,5 @@ int htruleRegister(char* ruletype, ...); ***/ #define ht_get_fl_t ht_get_fl_y -/*** Alternate formula suggested in the GitHub Issue. I think this should work - *** and be more efficient, but for some reason it doesn't at all, and I can't - *** understand why. I probably just implemented it wrong. - ***/ -// #define ht_flex_format "calc(%INTpx + (100%% - %INTpx) * %DBL)" -// #define ht_flex(size, total, flex) (size), (total), (double)(flex) / 100.0 - #endif /* _HT_RENDER_H */ diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 619736703..f51d508b1 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -222,7 +222,7 @@ int i=0, count=0; ((pWgtrNode)xaGetItem(&PatchedWidgets, i))->pre_height = -1; } - /** Free the PatchedWidgets XArray.**/ + /** Free the PatchedWidgets XArray. **/ xaDeInit(&PatchedWidgets); return 0; @@ -1282,9 +1282,9 @@ aposIsSpacer(pAposLine StartL, pAposLine EndL, int type, int isBorder) { pWgtrNode SW, EW; int i=0, j=0; -/** @brief The number of widgets starting at the end of this section.**/ +/** @brief The number of widgets starting at the end of this section. **/ int sCount=xaCount(&(EndL->SWidgets)); -/** @brief The number of widgets ending at the start of this section.**/ +/** @brief The number of widgets ending at the start of this section. **/ int eCount=xaCount(&(StartL->EWidgets)); if((EndL->Loc - StartL->Loc) <= APOS_MINSPACE) // If section is sufficiently narrow. @@ -1488,7 +1488,7 @@ float TotalSum=0; TotalSum += (FlexWeight * SizeWeight); } - /** The initial borders do not adjust.**/ + /** The initial borders do not adjust. **/ pAposLine leftBorder = (pAposLine)xaGetItem(Lines, 0); leftBorder->LocAdjWeight = leftBorder->MyAdjWeight = 0.0f; @@ -1506,26 +1506,30 @@ float TotalSum=0; ***/ float AdjWeight = PrevSect->AdjWeight = (float)(FlexWeight*SizeWeight)/TotalSum; - /** Store the line adjustment weight for responsive CSS later.**/ + /** Store the line adjustment weight for responsive CSS later. **/ CurrLine->LocAdjWeight = PrevLine->LocAdjWeight + AdjWeight; CurrLine->MyAdjWeight = AdjWeight; - /**for expanding lines**/ + /** Expand lines. **/ if(Diff > 0) { /** Calculate adjustment using the adjustment weight. **/ Adj = (float)(Diff) * AdjWeight + APOS_FUDGEFACTOR; - /** Apply the calculated adjustment.**/ + // printf("Expanding lines by %d*%f=%d\n", Diff, AdjWeight, Adj); + + /** Apply the calculated adjustment. **/ PrevSect->Width += Adj; CurrLine->Loc = PrevLine->Loc + PrevSect->Width; } - /**for contracting lines**/ + /** Contract lines. **/ else if(Diff < 0) { /** Calculate adjustment using the adjustment weight. **/ Adj = (float)(Diff) * AdjWeight - APOS_FUDGEFACTOR; + // printf("Contracting lines by %d*%f=%d\n", Diff, AdjWeight, Adj); + /** if the section width will be unacceptably *** narrow or negative after the adjustment **/ if((Adj + PrevSect->Width) <= APOS_MINSPACE) @@ -1608,7 +1612,7 @@ pWgtrNode Widget; aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); if(flag==APOS_ROW && Widget->fl_height) { - /** Calculate the new size, taking APOS_MINWIDTH into account.**/ + /** Calculate the new size, taking APOS_MINWIDTH into account. **/ newsize = CurrLine->Loc - Widget->y - isTopTab*24; if (newsize < APOS_MINWIDTH && Widget->pre_height >= APOS_MINWIDTH) Widget->height = APOS_MINWIDTH; @@ -1652,7 +1656,7 @@ pWgtrNode Widget; /** Adjusts width or height of widgets ending on this line. **/ count = xaCount(&(CurrLine->CWidgets)); - printf("Doing %d widgets.\n", count); + // printf("Doing %d widgets.\n", count); for(j=0; jCWidgets), j); From 5f168eeb0c9397b354ce60d5703a7e23398031b5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 30 Jul 2025 16:11:58 -0600 Subject: [PATCH 018/101] Working, server-side code. Fixes components, dropdowns, and images. Renames flex variables to be more clear. Send parent_w and parent_h to client. --- centrallix-os/sys/js/htdrv_pane.js | 4 ++ centrallix/htmlgen/ht_render.c | 94 ++++++++++++++++++++++------ centrallix/htmlgen/htdrv_component.c | 28 ++++++++- centrallix/htmlgen/htdrv_dropdown.c | 56 ++++++++++++++++- centrallix/htmlgen/htdrv_image.c | 33 ++++++++-- centrallix/htmlgen/htdrv_pane.c | 4 +- centrallix/include/apos.h | 24 +++---- centrallix/include/ht_render.h | 55 ++++++++++++---- centrallix/include/wgtr.h | 11 ++-- centrallix/wgtr/apos.c | 58 +++++++++-------- centrallix/wgtr/wgtr.c | 26 +++++--- 11 files changed, 293 insertions(+), 100 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_pane.js b/centrallix-os/sys/js/htdrv_pane.js index dfdb597ad..9489912e7 100644 --- a/centrallix-os/sys/js/htdrv_pane.js +++ b/centrallix-os/sys/js/htdrv_pane.js @@ -79,6 +79,10 @@ function pn_setbackground(aparam) function pn_action_resize(aparam) { + // I think this code is a better implementation of the following lines: + // var w = (aparam.Width) ?? pg_get_style(this, 'width'); + // var h = (aparam.Height) ?? pg_get_style(this, 'height'); + var w = aparam.Width?aparam.Width:pg_get_style(this, 'width'); var h = aparam.Height?aparam.Height:pg_get_style(this, 'height'); resizeTo(this, w, h); diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index c98f81b14..647374a67 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1500,10 +1500,12 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) htr_internal_WriteWgtrProperty(s, tree, "fl_y"); htr_internal_WriteWgtrProperty(s, tree, "fl_width"); htr_internal_WriteWgtrProperty(s, tree, "fl_height"); - htr_internal_WriteWgtrProperty(s, tree, "adj_weight_x"); - htr_internal_WriteWgtrProperty(s, tree, "adj_weight_y"); - htr_internal_WriteWgtrProperty(s, tree, "adj_weight_w"); - htr_internal_WriteWgtrProperty(s, tree, "adj_weight_h"); + htr_internal_WriteWgtrProperty(s, tree, "total_fl_x"); + htr_internal_WriteWgtrProperty(s, tree, "total_fl_y"); + htr_internal_WriteWgtrProperty(s, tree, "total_fl_w"); + htr_internal_WriteWgtrProperty(s, tree, "total_fl_h"); + htr_internal_WriteWgtrProperty(s, tree, "parent_w"); + htr_internal_WriteWgtrProperty(s, tree, "parent_h"); } propname = wgtrFirstPropertyName(tree); while(propname) @@ -2748,30 +2750,82 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y int ht_get_total_w__INTERNAL(pWgtrNode widget) { + /** Check to see if the value was already cached by a previous call. **/ + int cached_value = widget->parent_w; + if (cached_value != -1) { +// printf( +// "Got total width available to '%s' (%s) from cache: %dpx\n", +// widget->Name, widget->Type, cached_value +// ); + return cached_value; + } + + // DEBUG + if (widget->Parent == NULL) { + printf("\nPANIC: Call to ht_get_total_w__INTERNAL() on widget with no parent!\n\n"); + wgtrPrint(widget, 1); + } + + /** Cache miss, we'll need to traverse to the parent and find its width manually. */ pWgtrNode parent = widget->Parent; int parentWidth = parent->width; -// printf( -// "Getting total width available to '%s' (%s), child of '%s' (%s) - %dpx\n", -// widget->Name, widget->Type, parent->Name, parent->Type, parentWidth -// ); - if (parentWidth >= 0) { +// int isParentVisual = !(parent->Flags & WGTR_F_NONVISUAL); +// printf( +// "Getting total width available to '%s' (%s), child of '%s' (%s) - %dpx %d\n", +// widget->Name, widget->Type, parent->Name, parent->Type, parentWidth, isParentVisual +// ); + + /** Check if the parent has a width value. **/ + if (parentWidth >= 0 /* && isParentVisual */) { int offset = parent->left + parent->right, ret = parentWidth - offset; - // printf("Returning %d-%d=%d\n", parentWidth, offset, ret); - return ret; - } else return ht_get_total_w(parent); // Tail-recursion + printf("Returning %d-%d=%d\n", parentWidth, offset, ret); + return (widget->parent_w = ret); + } else { + if (parent->Parent == NULL) { + printf("Recursive call would segfault! Guessing %dpx instead.\n", parentWidth); + return (widget->parent_w = parentWidth); + } + return (widget->parent_w = ht_get_total_w(parent)); + } } int ht_get_total_h__INTERNAL(pWgtrNode widget) { + /** Check to see if the value was already cached by a previous call. **/ + int cached_value = widget->parent_h; + if (cached_value != -1) { +// printf( +// "Got total height available to '%s' (%s) from cache: %dpx\n", +// widget->Name, widget->Type, cached_value +// ); + return cached_value; + } + + // DEBUG + if (widget->Parent == NULL) { + printf("\nPANIC: Call to ht_get_total_h__INTERNAL() on widget with no parent!\n\n"); + wgtrPrint(widget, 1); + } + + /** Cache miss, we'll need to traverse to the parent and find its height manually. */ pWgtrNode parent = widget->Parent; int parentHeight = parent->height; -// printf( -// "Getting total height available to '%s' (%s), child of '%s' (%s) - %dpx\n", -// widget->Name, widget->Type, parent->Name, parent->Type, parentHeight -// ); - if (parentHeight >= 0) { +// int isParentVisual = !(parent->Flags & WGTR_F_NONVISUAL); +// printf( +// "Getting total height available to '%s' (%s), child of '%s' (%s) - %dpx %d\n", +// widget->Name, widget->Type, parent->Name, parent->Type, parentHeight, isParentVisual +// ); + + /** Check if the parent has a height value. **/ + if (parentHeight >= 0 /* && isParentVisual */) { int offset = parent->top + parent->bottom, ret = parentHeight - offset; - // printf("Returning %d-%d=%d\n", parentHeight, offset, ret); - return ret; - } else return ht_get_total_h(parent); // Tail-recursion + printf("Returning %d-%d=%d\n", parentHeight, offset, ret); + return (widget->parent_h = ret); + } else { + if (parent->Parent == NULL) { + printf("Recursive call would segfault! Guessing %dpx instead.\n", parentHeight); + return (widget->parent_h = parentHeight); + } + return (widget->parent_h = ht_get_total_h__INTERNAL(parent)); + } } diff --git a/centrallix/htmlgen/htdrv_component.c b/centrallix/htmlgen/htdrv_component.c index 93c5533a5..14988d1a4 100644 --- a/centrallix/htmlgen/htdrv_component.c +++ b/centrallix/htmlgen/htdrv_component.c @@ -353,8 +353,31 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) mssError(0,"HTCMP","Invalid component for widget '%s'",name); goto out; } - wgtrMoveChildren(cmp_tree, x, y); + /*** Adjusting children is no longer necessary because + *** the component is placed inside an isolating div. + *** Thus, the children's location within this div will + *** be correct without adjustment. + ***/ + // wgtrMoveChildren(cmp_tree, x, y); + + /** Style enclosing div. **/ + htrAddStylesheetItem_va(s, + "\t#cmp%POSbase { " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "OVERFLOW:hidden; " + ht_flex_format_all + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex_all(x, y, w, h, tree), + z + ); + + /** Enclosing div to isolate children. **/ + htrAddBodyItem_va(s,"
\n", id); + /** Check param references **/ htcmp_internal_CheckReferences(cmp_tree, params, s->Namespace->DName); @@ -369,6 +392,9 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Switch the namespace back **/ htrLeaveNamespace(s); + /** End the containing layer. **/ + htrAddBodyItem(s, "
\n"); + /** End Init component **/ htrAddScriptInit_va(s, " cmp_endinit(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", name); diff --git a/centrallix/htmlgen/htdrv_dropdown.c b/centrallix/htmlgen/htdrv_dropdown.c index 335fe7f48..7f16bcbe5 100644 --- a/centrallix/htmlgen/htdrv_dropdown.c +++ b/centrallix/htmlgen/htdrv_dropdown.c @@ -145,12 +145,62 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { strtcpy(name,ptr,sizeof(name)); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#dd%POSbtn { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:%POSpx; WIDTH:%POSpx; Z-INDEX:%POS; cursor:default; background-color: %STR&CSSVAL; border:1px outset #e0e0e0;}\n",id,x,y,h,w,z,bgstr); + htrAddStylesheetItem_va(s, + "\t#dd%POSbtn { " + "OVERFLOW:hidden; " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:"ht_flex_format"; " + "TOP:"ht_flex_format"; " + "WIDTH:"ht_flex_format"; " + "HEIGHT:"ht_flex_format"; " + "Z-INDEX:%POS; " + "cursor:default; " + "background-color: %STR&CSSVAL; " + "border:1px outset #e0e0e0; " + "}\n", + id, + ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), + ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + z, + bgstr + ); if (*textcolor) { htrAddStylesheetItem_va(s,"\t#dd%POSbtn { color: %STR&CSSVAL; }\n",id,textcolor); } - htrAddStylesheetItem_va(s,"\t#dd%POScon1 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:1px; TOP:1px; WIDTH:1024px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,h-2,z+1); - htrAddStylesheetItem_va(s,"\t#dd%POScon2 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:1px; TOP:1px; WIDTH:1024px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,h-2,z+1); + htrAddStylesheetItem_va(s, + "\t#dd%POScon1 { " + "OVERFLOW:hidden; " + "POSITION:absolute; " + "VISIBILITY:inherit; " + "LEFT:1px; " + "TOP:1px; " + "WIDTH:1024px; " + "HEIGHT:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(h-2, h, 0.0), + z+1 + ); + /** I have no idea why we need dd#con2. It's hidden by default. **/ + htrAddStylesheetItem_va(s, + "\t#dd%POScon2 { " + "OVERFLOW:hidden; " + "POSITION:absolute; " + "VISIBILITY:hidden; " + "LEFT:1px; " + "TOP:1px; " + "WIDTH:1024px; " + "HEIGHT:"ht_flex_format"; " + "Z-INDEX:%POS; " + "}\n", + id, + ht_flex(h-2, h, 0.0), + z+1 + ); htrAddScriptGlobal(s, "dd_current", "null", 0); htrAddScriptGlobal(s, "dd_lastkey", "null", 0); diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index 7ad156f71..c46c66219 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -178,14 +178,39 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) /** HTML body
element for the base layer. **/ if (!strcmp(aspect, "stretch")) { - htrAddBodyItemLayer_va(s, 0, "img%POS", id, "wimage", - "\n\n", + htrAddBodyItemLayer_va(s, + 0, "img%POS", id, "wimage", + "\n\n", id, w, h, src); } else // "preserve" { - htrAddBodyItemLayer_va(s, 0, "img%POS", id, "wimage", - "\n\n", + htrAddBodyItemLayer_va(s, + 0, "img%POS", id, "wimage", + "\n\n", id, w, h, src); } diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 0caf96258..9bd8a6759 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -187,7 +187,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) "\t#pn%POSmain {" "POSITION:absolute; " "VISIBILITY:inherit; " - "overflow:hidden; " + "OVERFLOW:hidden; " "LEFT:"ht_flex_format"; " "TOP:"ht_flex_format"; " "WIDTH:"ht_flex_format"; " @@ -234,7 +234,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) /** HTML body
element for the base layer. **/ //htrAddBodyItem_va(s,"
\n",id, w-2, h-2); - htrAddBodyItem_va(s,"
\n",id, w-2, h-2); + htrAddBodyItem_va(s,"
\n", id); /** Check for objects within the pane. **/ htrRenderSubwidgets(s, tree, z+2); diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index da8792946..74fe8adf1 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -53,12 +53,12 @@ struct _APOS_L pAposSection ESection; // section ending with this line /*** Used to find the distance the line should move when the parent - *** container is resized. LocAdjWeight is the weight that this line - *** moves relative to the container. MyAdjWeight is the amount that - *** this line moves relative to the line before (left of) it. + *** container is resized. loc_fl (local flex) is the weight that this + *** line moves relative to the container. my_fl (my flex) is the amount + *** that this line moves relative to the line before (left of) it. *** Used for generating responsive CSS. ***/ - float LocAdjWeight, MyAdjWeight; + float loc_fl, my_fl; }; /**Section Structure (used for both rows and columns)**/ @@ -71,19 +71,6 @@ struct _APOS_S int DesiredWidth; //When we need to resize to honor max/mins int isSpacer; //set for narrow spaces between widgets int isBorder; //set for grid border sections - - /*** Computed value which stores the weight for how much this section - *** should be adjusted when resizing. All sections along each dimention - *** inside a single page or other container should have a total adjWeight - *** of 1.0. - *** - *** For example, if we have two sections with adj weights of 0.4 and 0.6. - *** If the container/page is stretched by 10 px, then 4 px is distributed - *** to the first section and 6 px is distributed to the second section. - *** - *** Warning: Currently unused. - ***/ - float AdjWeight; }; /**Grid Structure**/ @@ -175,4 +162,7 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t #define APOS_EGAPFLEX 30 #define APOS_CGAPFLEX 50 +/** Macros for readability and anticipation-of-change. **/ +#define isScrollpane(Parent) (!strcmp((Parent)->Type, "widget/scrollpane")) + #endif diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index b725eb9d4..02d32ec6d 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -396,34 +396,37 @@ int htruleRegister(char* ruletype, ...); ***/ #define ht_flex(size, total, flex) (size), (total), (flex) +/** ====[ Macros for getting total container size ]==== **/ +int ht_get_total_w__INTERNAL(pWgtrNode widget); +int ht_get_total_h__INTERNAL(pWgtrNode widget); + +#define ht_get_total_w(widget) ht_get_total_w__INTERNAL(widget) +#define ht_get_total_h(widget) ht_get_total_h__INTERNAL(widget) + +// #define ht_get_total_w(widget) ((widget)->Parent->width - (widget)->Parent->left - (widget)->Parent->right) +// #define ht_get_total_h(widget) ((widget)->Parent->height - (widget)->Parent->top - (widget)->Parent->bottom) + +/** ====[ Macros for getting total flexibilities ]==== **/ + /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the x direction. ***/ -#define ht_get_fl_x(widget) ((widget)->xAdjWeight) +#define ht_get_fl_x(widget) ((widget)->total_fl_x) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the y direction. ***/ -#define ht_get_fl_y(widget) ((widget)->yAdjWeight) +#define ht_get_fl_y(widget) ((widget)->total_fl_y) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the width direction. ***/ -#define ht_get_fl_w(widget) ((widget)->wAdjWeight) +#define ht_get_fl_w(widget) ((widget)->total_fl_w) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the height direction. ***/ -#define ht_get_fl_h(widget) ((widget)->hAdjWeight) - -int ht_get_total_w__INTERNAL(pWgtrNode widget); -int ht_get_total_h__INTERNAL(pWgtrNode widget); - -#define ht_get_total_w(widget) ht_get_total_w__INTERNAL(widget) -#define ht_get_total_h(widget) ht_get_total_h__INTERNAL(widget) - -// #define ht_get_total_w(widget) ((widget)->Parent->width - (widget)->Parent->left - (widget)->Parent->right) -// #define ht_get_total_h(widget) ((widget)->Parent->height - (widget)->Parent->top - (widget)->Parent->bottom) +#define ht_get_fl_h(widget) ((widget)->total_fl_h) /*** @brief A shortcut function to get the flexibility when writing the *** LEFT CSS attribute. @@ -439,5 +442,31 @@ int ht_get_total_h__INTERNAL(pWgtrNode widget); ***/ #define ht_get_fl_t ht_get_fl_y +/** ====[ Macros for being lazy ]==== **/ + +#define ht_flex_x(x, widget) ht_flex(x, ht_get_total_w(widget), ht_get_fl_x(widget)) +#define ht_flex_y(y, widget) ht_flex(y, ht_get_total_h(widget), ht_get_fl_y(widget)) +#define ht_flex_w(w, widget) ht_flex(w, ht_get_total_w(widget), ht_get_fl_w(widget)) +#define ht_flex_h(h, widget) ht_flex(h, ht_get_total_h(widget), ht_get_fl_h(widget)) + +#define ht_flex_format_all \ + "LEFT:"ht_flex_format"; " \ + "TOP:"ht_flex_format"; " \ + "WIDTH:"ht_flex_format"; " \ + "HEIGHT:"ht_flex_format"; " \ + +#define ht_flex_all(x, y, w, h, widget) \ + ht_flex_x(x, widget), \ + ht_flex_y(y, widget), \ + ht_flex_w(w, widget), \ + ht_flex_h(h, widget) \ + +// Workaround because -lm isn't passed to my editor and this was the easiest way to fix it. +// This code should not appear in a pull request. If you are a code reviewer, remove this +// code or reject the PR. +#ifndef M_PI +#define M_PI 3.14159 +#endif + #endif /* _HT_RENDER_H */ diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index f11584f0d..298bf5dc9 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -100,16 +100,17 @@ typedef struct _WN char Namespace[64]; /** Namespace this widget and subwidgets are in **/ int r_x, r_y, r_width, r_height; /** Requested geometry **/ int pre_x, pre_y, pre_width, pre_height; /** pre-layout geom. **/ - int fl_x, fl_y, fl_width, fl_height;/** Flexibility **/ - double fx, fy, fw, fh; /** internal flexibility calculations **/ - double xAdjWeight, yAdjWeight; /** Responsive CSS adjustment weights for x and y */ - double wAdjWeight, hAdjWeight; /** Responsive CSS adjustment weights for width and height */ + int fl_x, fl_y, fl_width, fl_height;/** Flexibilities as specified by the designer **/ + double fx, fy, fw, fh; /** internal flexibility calculations **/ + double total_fl_x, total_fl_y; /** Total flexiblities as calculated for this layout. */ + double total_fl_w, total_fl_h; /** Responsive CSS adjustment weights for width and height */ + int parent_w, parent_h; /** The expected size of the parent container */ int min_width, min_height; /** absolute minimums **/ int x, y, width, height; /** actual geometry **/ int top, bottom, left, right; /** container offsets **/ XArray Properties; /** Array of widget properties **/ XArray Children; /** Array of child widgets **/ - struct _WN* Parent; + struct _WN* Parent; struct _WN* Root; int CurrProperty; /** Property to return on next call to wgtNextProperty **/ int CurrChild; /** Child to return on next call to wgtrNextChild **/ diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index f51d508b1..29a1b88ec 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -31,6 +31,8 @@ /*** Author: Israel Fuller *** Date: June, 2025 + *** + *** See also: Auto-Positioning.md *** *** I wasn't the one to write most of this (although I did write a ton of *** comments), but after doing my best to understand it, I hope that you will @@ -123,6 +125,11 @@ int i, childCnt, sectionCnt; pAposSection section; pWgtrNode child; + /*** Note: The "%*.*s" format specifier here takes two parameters: + *** - The first (indent*4) specifies the minimum width (number of spaces). + *** - The second (indent*4) limits the maximum number of characters to print. + *** Essentially, this adds (indent*4) spaces to the start of the line. + ***/ printf("%*.*s*** %s ***\n", indent*4, indent*4, "", tree->Name); if (tree->LayoutGrid) { @@ -448,7 +455,7 @@ int i=0, childCount=xaCount(&(Parent->Children)); *** Remember here that strcmp() returns 0 (false) if the strings are equal. ***/ if((Child->height < 0) && !(Child->Flags & WGTR_F_NONVISUAL) && - strcmp(Parent->Type, "widget/scrollpane")) + !isScrollpane(Parent)) aposPatchNegativeHeight(Child, PatchedWidgets); /** If child is a container, but not a floating window, recursively prepare it as well. **/ @@ -587,7 +594,7 @@ aposSetOffsetBools(pWgtrNode W, int *isSP, int *isWin, int *isTopTab, int *isSid ObjData val; /** Set isSP to compensate for scrollpane scrollbars. **/ - if(isSP) *isSP = (!strcmp(W->Type, "widget/scrollpane")); + if(isSP) *isSP = (isScrollpane(W)); /** Set isWin to compensate windows' titlebars, if any. **/ if(isWin && !strcmp(W->Type, "widget/childwindow")) @@ -838,7 +845,7 @@ pXArray FirstCross, LastCross; height_adj = Parent->min_height - Parent->pre_height; /** Add the 2 horizontal border lines, unless parent is a scrollpane. **/ - if(strcmp(Parent->Type, "widget/scrollpane")) + if(!isScrollpane(Parent)) { int minHeightLoc = 0, maxHeightLoc = Parent->pre_height - isWin * 24; if(aposCreateLine(NULL, HLines, minHeightLoc, APOS_NOT_LINKED, APOS_IS_BORDER, 0, APOS_HORIZONTAL) < 0) @@ -955,9 +962,10 @@ pWgtrNode C; else if(!(C->Flags & WGTR_F_NONVISUAL) && !(C->Flags & WGTR_F_FLOATING)) { /** Add horizontal lines, unless parent is a scrollpane. **/ - if(strcmp(Parent->Type, "widget/scrollpane")) + if(!isScrollpane(Parent)) { - /*** From this code, we see that the start line is + /*** Note: + *** From this code, we see that the start line is *** always the minY, and the end of the line is *** always the maxY. Thus, the top line is the *** start line and the bottom line is the end line @@ -969,14 +977,15 @@ pWgtrNode C; if(aposCreateLine(C, HLines, maxY, APOS_EWIDGETS, APOS_NOT_BORDER, height_adj, APOS_HORIZONTAL) < 0) goto CreateLineError; } - - /** Add vertical lines. **/ - /*** From this code, we see that the start line is always + + /*** Note: + *** From this code, we see that the start line is always *** the minX, and the end of the line is always the maxX. *** Thus, the left line is the start line and the right *** line is the end line because X increases as we move *** right along the page. ***/ + /** Add vertical lines. **/ int minX = (C->x), maxX = (C->x + C->width + isSideTab*tabWidth); if(aposCreateLine(C, VLines, minX, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_VERTICAL) < 0) goto CreateLineError; @@ -1465,10 +1474,9 @@ float TotalSum=0; TotalFlex += CurrSect->Flex; if(CurrSect->Flex) { - FlexibleSections++; - TotalFlexibleSpace += CurrSect->Width; + FlexibleSections++; + TotalFlexibleSpace += CurrSect->Width; } - else CurrSect->AdjWeight = 0.0f; } /*** If there is no flexibility (no expansion or contraction), we can't @@ -1490,7 +1498,7 @@ float TotalSum=0; /** The initial borders do not adjust. **/ pAposLine leftBorder = (pAposLine)xaGetItem(Lines, 0); - leftBorder->LocAdjWeight = leftBorder->MyAdjWeight = 0.0f; + leftBorder->loc_fl = leftBorder->my_fl = 0.0f; for(i=1; iAdjWeight = (float)(FlexWeight*SizeWeight)/TotalSum; + float fl = (float)(FlexWeight * SizeWeight) / TotalSum; /** Store the line adjustment weight for responsive CSS later. **/ - CurrLine->LocAdjWeight = PrevLine->LocAdjWeight + AdjWeight; - CurrLine->MyAdjWeight = AdjWeight; + CurrLine->loc_fl = PrevLine->loc_fl + fl; + CurrLine->my_fl = fl; /** Expand lines. **/ if(Diff > 0) { /** Calculate adjustment using the adjustment weight. **/ - Adj = (float)(Diff) * AdjWeight + APOS_FUDGEFACTOR; + Adj = (float)(Diff) * fl + APOS_FUDGEFACTOR; - // printf("Expanding lines by %d*%f=%d\n", Diff, AdjWeight, Adj); + // printf("Expanding lines by %d*%f=%d\n", Diff, fl, Adj); /** Apply the calculated adjustment. **/ PrevSect->Width += Adj; @@ -1526,9 +1534,9 @@ float TotalSum=0; else if(Diff < 0) { /** Calculate adjustment using the adjustment weight. **/ - Adj = (float)(Diff) * AdjWeight - APOS_FUDGEFACTOR; + Adj = (float)(Diff) * fl - APOS_FUDGEFACTOR; - // printf("Contracting lines by %d*%f=%d\n", Diff, AdjWeight, Adj); + // printf("Contracting lines by %d*%f=%d\n", Diff, fl, Adj); /** if the section width will be unacceptably *** narrow or negative after the adjustment **/ @@ -1596,11 +1604,11 @@ pWgtrNode Widget; Widget = (pWgtrNode)xaGetItem(&(CurrLine->SWidgets), j); if(flag == APOS_ROW) { Widget->y = CurrLine->Loc; - Widget->yAdjWeight = CurrLine->LocAdjWeight; + Widget->total_fl_y = CurrLine->loc_fl; } else { Widget->x = CurrLine->Loc; - Widget->xAdjWeight = CurrLine->LocAdjWeight; + Widget->total_fl_x = CurrLine->loc_fl; } } @@ -1627,7 +1635,7 @@ pWgtrNode Widget; *** to problems down the road, but I plan to fix *** them if and when I encounter them. ***/ - Widget->hAdjWeight += CurrLine->MyAdjWeight; + Widget->total_fl_h += CurrLine->my_fl; } else if(flag==APOS_COL && Widget->fl_width) { @@ -1650,7 +1658,7 @@ pWgtrNode Widget; *** to problems down the road, but I plan to fix *** them if and when I encounter them. ***/ - Widget->wAdjWeight += CurrLine->MyAdjWeight; + Widget->total_fl_w += CurrLine->my_fl; } } @@ -1661,9 +1669,9 @@ pWgtrNode Widget; { Widget = (pWgtrNode)xaGetItem(&(CurrLine->CWidgets), j); if(flag==APOS_ROW && Widget->fl_height) - Widget->hAdjWeight += CurrLine->MyAdjWeight; + Widget->total_fl_h += CurrLine->my_fl; else if(flag==APOS_COL && Widget->fl_width) - Widget->wAdjWeight += CurrLine->MyAdjWeight; + Widget->total_fl_w += CurrLine->my_fl; } } diff --git a/centrallix/wgtr/wgtr.c b/centrallix/wgtr/wgtr.c index 0fa312f19..5723c67eb 100755 --- a/centrallix/wgtr/wgtr.c +++ b/centrallix/wgtr/wgtr.c @@ -1310,10 +1310,11 @@ wgtrGetPropertyType(pWgtrNode widget, char* name) else if (!strcmp(name, "outer_type")) return DATA_T_STRING; else if (!strcmp(name, "x") || !strcmp(name, "y") || !strcmp(name, "width") || !strcmp(name, "height") || !strcmp(name, "r_x") || !strcmp(name, "r_y") || !strcmp(name, "r_width") || !strcmp(name, "r_height") || - !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height")) + !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height") || + !strcmp(name, "parent_w") || !strcmp(name, "parent_h")) return DATA_T_INTEGER; - else if (!strcmp(name, "adj_weight_x") || !strcmp(name, "adj_weight_y") || - !strcmp(name, "adj_weight_w") || !strcmp(name, "adj_weight_h") || + else if (!strcmp(name, "total_fl_x") || !strcmp(name, "total_fl_y") || + !strcmp(name, "total_fl_w") || !strcmp(name, "total_fl_h") || !strcmp(name, "fx") || !strcmp(name, "fy") || !strcmp(name, "fw") || !strcmp(name, "fh")) return DATA_T_DOUBLE; count = xaCount(&(widget->Properties)); @@ -1356,6 +1357,11 @@ wgtrGetPropertyValue(pWgtrNode widget, char* name, int datatype, pObjData val) if (!strcmp(name+3, "width")) { val->Integer = widget->fl_width; return 0; } if (!strcmp(name+3, "height")) { val->Integer = widget->fl_height; return 0; } } + else if (!strncmp(name, "parent_", 7)) + { + if (!strcmp(name+7, "w")) { val->Integer = widget->parent_w; return 0; } + if (!strcmp(name+7, "h")) { val->Integer = widget->parent_h; return 0; } + } } if (datatype == DATA_T_DOUBLE) { @@ -1366,12 +1372,12 @@ wgtrGetPropertyValue(pWgtrNode widget, char* name, int datatype, pObjData val) else if (!strcmp(name+1, "w")) { val->Double = widget->fw; return 0; } else if (!strcmp(name+1, "h")) { val->Double = widget->fh; return 0; } } - else if (!strncmp(name, "adj_weight_", 11)) + else if (!strncmp(name, "total_fl_", 9)) { - if (!strcmp(name+11, "x")) { val->Double = (double)widget->xAdjWeight; return 0; } - else if (!strcmp(name+11, "y")) { val->Double = (double)widget->yAdjWeight; return 0; } - else if (!strcmp(name+11, "w")) { val->Double = (double)widget->wAdjWeight; return 0; } - else if (!strcmp(name+11, "h")) { val->Double = (double)widget->hAdjWeight; return 0; } + if (!strcmp(name+9, "x")) { val->Double = (double)widget->total_fl_x; return 0; } + else if (!strcmp(name+9, "y")) { val->Double = (double)widget->total_fl_y; return 0; } + else if (!strcmp(name+9, "w")) { val->Double = (double)widget->total_fl_w; return 0; } + else if (!strcmp(name+9, "h")) { val->Double = (double)widget->total_fl_h; return 0; } } } else if (datatype == DATA_T_STRING) @@ -1592,10 +1598,10 @@ wgtrNewNode( char* name, char* type, pObjSession s, node->fl_y = fly; node->fl_width = flwidth; node->fl_height = flheight; + node->parent_h = node->parent_w = -1; node->ObjSession = s; node->Parent = NULL; - node->min_height = 0; - node->min_width = 0; + node->min_height = node->min_width = 0; node->LayoutGrid = NULL; node->Root = node; /* this will change when it is added as a child */ node->DMPrivate = NULL; From 65ff720039a5c0771e720877ce34b3eb0a865383 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 12:45:48 -0700 Subject: [PATCH 019/101] Improve apos.c & add design support. Add tab-height support to apos.c. Add design support to apos.c. Add IsDesign to the WgtrClientInfo struct. Fix spelling mistakes. Clean up. --- centrallix/include/apos.h | 19 ++-- centrallix/include/wgtr.h | 1 + centrallix/netdrivers/net_http.c | 3 + centrallix/wgtr/apos.c | 174 ++++++++++++++++--------------- 4 files changed, 106 insertions(+), 91 deletions(-) diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index 74fe8adf1..0333421cc 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -92,7 +92,7 @@ int aposInit(); /**Registers datastructures used in auto-positioning**/ int aposInitiallizeGrid (pAposGrid); /**Initiallizes the XArrays in the grid object**/ int aposFree(pAposGrid); /**Frees dynamically allocated memory**/ int aposFreeGrids(pWgtrNode); /**Frees dynamically allocated memory**/ -int aposSetOffsetBools(pWgtrNode, int*, int*, int*, int*, int*); /**sets bools used to offset widgets**/ +int aposSetOffsetBools(pWgtrNode, int*, int*, int*, int*, int*, int*); /**sets bools used to offset widgets**/ int aposBuildGrid(pWgtrNode); /** builds the layout grids **/ int aposSetLimits(pWgtrNode); /** enforce min/max sizing **/ @@ -120,17 +120,18 @@ int aposMinimumChildFlex(pAposLine, int); /**Returns minimum flexibility of wid /**Resizing and Repositioning**/ int aposSpaceOutLines(pXArray, pXArray, int); /**Adjusts spaces between lines to expand or contract grid**/ -int aposSnapWidgetsToGrid(pXArray, int); /**Refreshes widget dimensions to match adjusted grid**/ +int aposSnapWidgetsToGrid(pXArray, int, pWgtrClientInfo); /**Refreshes widget dimensions to match adjusted grid**/ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree to process windows**/ -/** # defines names for magic values to make them easier to read. **/ -/** Indicates how a line links to a widget. */ +/** #define names for values to improve readability. **/ + +/** Indicates how a line links to a widget. **/ #define APOS_NOT_LINKED 0 #define APOS_SWIDGETS 1 #define APOS_EWIDGETS 2 -/** Indicates if a line is vertical. */ +/** Indicates if a line is vertical. **/ #define APOS_VERTICAL 1 #define APOS_HORIZONTAL 0 @@ -141,11 +142,11 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t #define APOS_ROW 1 #define APOS_COL 2 -/** Indicates if a line is a border. */ +/** Indicates if a line is a border. **/ #define APOS_IS_BORDER 1 #define APOS_NOT_BORDER 0 -/** Allows rounding when casting floats or doubles to ints. */ +/** Allows rounding when casting floats or doubles to ints. **/ #define APOS_FUDGEFACTOR 0.5 /*** The greatest width between two widgets that still defines them as @@ -159,6 +160,10 @@ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree t #define APOS_MINWIDTH 30 /** Default flexibilities for widgetless gaps in expanding or contracting applications. **/ +/*** Israel: I don't know the difference between these two values. I'm guessing + *** E stands enlarge and C stands for contract, thus (30, 50) makes it + *** easier for gaps to grown than for them to shrink. + ***/ #define APOS_EGAPFLEX 30 #define APOS_CGAPFLEX 50 diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index 298bf5dc9..d9a89b1dc 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -69,6 +69,7 @@ typedef struct int CharWidth; int CharHeight; int ParagraphHeight; /* total height of one line of text */ + int IsDesign; /* 1 if the page is rendered with cx__geom=design, 0 otherwise. */ char AKey[256]; char* Templates[WGTR_MAX_TEMPLATE]; char* Overlays[WGTR_MAX_OVERLAY]; diff --git a/centrallix/netdrivers/net_http.c b/centrallix/netdrivers/net_http.c index c74bc121b..17c6a464c 100755 --- a/centrallix/netdrivers/net_http.c +++ b/centrallix/netdrivers/net_http.c @@ -2096,6 +2096,9 @@ nht_i_GET(pNhtConn conn, pStruct url_inf, char* if_modified_since) wgtr_params.CharWidth = 7; wgtr_params.CharHeight = 16; wgtr_params.ParagraphHeight = 16; + + /** Specify design geometry **/ + wgtr_params.IsDesign = 1; } else { diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 29a1b88ec..06550eda0 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -31,13 +31,13 @@ /*** Author: Israel Fuller *** Date: June, 2025 - *** + *** *** See also: Auto-Positioning.md *** *** I wasn't the one to write most of this (although I did write a ton of *** comments), but after doing my best to understand it, I hope that you will *** find the compiled information below helpful. - *** + *** *** Execution of this file usually begins when wgtrVerify() in wgtr.c calls *** aposAutoPositionWidgetTree(). To auto position the tree, the code first *** it draws four lines on the four edges of every visible widget (with some @@ -49,25 +49,25 @@ *** small amounts of space intended to provide visual room between widgets. *** When resizing, these do not flex at all. However, many elements are able *** to flex. @see aposSetFlexibilities() for more information about flexing. - *** + *** *** Next, the program uses aposSetLimits() to honor minimum and maximum sizes *** of widgets, and finally calls aposAutoPositionContainers() to position *** the widgets on the screen. Lastly, it calls aposProcessWindows() to handle *** floating window widgets, which are typically ignored by most of the rest *** of the code. - *** + *** *** Note: Due to this approach, this means that all sections and widgets start - *** and end at a line. The way these lines are set up ensures that start - *** lines are always on the top or left, and end lines are always on the - *** bottom or right. @see aposAddLinesForChildren() - *** - *** Notes: I wrote some information about various structs below that's good to - *** know. Some of this is covered elsewhere in the documentation. + *** and end at a line. The way these lines are set up ensures that start + *** lines are always on the top or left, and end lines are always on the + *** bottom or right. @see aposAddLinesForChildren() + *** + *** Note: I wrote some information about various structs below that's good to + *** know. Some of this is covered elsewhere in the documentation. *** *** AposGrid: A data structure to store sections and lines. - *** + *** *** AposLine: An AposLine spans the entire page. - *** + *** *** AposSection: After lines are created, sections are added in between the *** lines (aka. in between the nodes). Every node begins and ends on the *** edge of a section, although it may span multiple sections. @@ -80,7 +80,7 @@ *** *** XArray: This array also stores its size (nAlloc) and the number of items *** stored (nItems), so you don't have to pass that info separately. - *** + *** *** SWidgets, CWidgets, and EWidgets: Lines record which widgets start, cross, *** and end on them. These categories are exclusive, so a widget which *** starts on a given line will be in the SWidgets list but it will not be @@ -295,11 +295,11 @@ int sectCount; return 0; } -/*** Adjusts space to acomodate children, somehow? I think? +/*** Adjusts space to accommodate children, somehow? I think? *** *** @param Parent The widget node parent who's limits are being calculated. - *** @param delta_w The change in width required to accomodate children. - *** @param delta_h The change in height required to accomodate children. + *** @param delta_w The change in width required to accommodate children. + *** @param delta_h The change in height required to accommodate children. *** @returns 0 if successful, -1 otherwise. ***/ int @@ -406,7 +406,7 @@ pWgtrNode Child; return 0; } -/*** Adjusts space to acomodate children, somehow? I think? +/*** Adjusts space to accommodate children, somehow? I think? *** *** @param Parent The widget node parent who's limits are being calculated. *** @returns 0, success. @@ -543,7 +543,7 @@ int i=0, sectCount=0, TotalWidth=0, ProductSum=0; /*** Calculate average row flexibility, weighted by height. *** Note: Section height is called width here because rows - *** are one dimentional and the feild is reused. + *** are one dimensional and the feild is reused. ***/ sectCount = xaCount(&(theGrid->Rows)); for(i=0; iType, "widget/tab")) + if((isTopTab != NULL || isSideTab != NULL) && strcmp(W->Type, "widget/tab") == 0) { /*** Set isTopTab and isSideTab. If the node does not specify the *** tab location, assume it has a top tab and leave side-tab unset. **/ if(wgtrGetPropertyValue(W, "tab_location", DATA_T_STRING, &val) < 0) - *isTopTab = 1; // Property not found, assume it has a top tab only. + { + if (isTopTab != NULL) *isTopTab = 1; // Property not found, assume it has a top tab only. + } else { *isTopTab = (!strcmp(val.String, "top") || !strcmp(val.String, "bottom")); - *isSideTab = (!strcmp(val.String, "left") || (!strcmp(val.String, "right"))); // Warning: Unchecked assignment. + *isSideTab = (!strcmp(val.String, "left") || (!strcmp(val.String, "right"))); } - - /** Set the tab width. If none is specified, default to 80. **/ - if(wgtrGetPropertyValue(W, "tab_width", DATA_T_INTEGER, &val) < 0) - *tabWidth = 80; - else *tabWidth = val.Integer; } + + /** Set the tab width and height (if needed), defaulting to 24 and 80 if unspecified. **/ + if(tabWidth != NULL) *tabWidth = (wgtrGetPropertyValue(W, "tab_width", DATA_T_INTEGER, &val) == 0) ? val.Integer : 80; + if(tabHeight != NULL) *tabHeight = (wgtrGetPropertyValue(W, "tab_height", DATA_T_INTEGER, &val) == 0) ? val.Integer : 24; return 0; } @@ -703,7 +705,7 @@ pAposGrid theGrid = NULL; /*** Recursively auto-positions containers and their children based on their grids. *** - *** Note: Assumes that the grild was already built with a call to aposBuildGrid(). + *** Note: Assumes that the grid was already built with a call to aposBuildGrid(). *** *** @param Parent The parent node who's containers are being autopositioned. *** @returns 0 if successful, -1 otherwise. @@ -739,9 +741,9 @@ int rows_extra=0, cols_extra=0; /**modify the widgets' x,y,w, and h values to snap to their adjusted lines**/ if (!(Parent->Flags & WGTR_F_VSCROLLABLE)) - aposSnapWidgetsToGrid(&(theGrid->HLines), APOS_ROW); //rows + aposSnapWidgetsToGrid(&(theGrid->HLines), APOS_ROW, Parent->Root->ClientInfo); //rows if (!(Parent->Flags & WGTR_F_HSCROLLABLE)) - aposSnapWidgetsToGrid(&(theGrid->VLines), APOS_COL); //columns + aposSnapWidgetsToGrid(&(theGrid->VLines), APOS_COL, Parent->Root->ClientInfo); //columns /** did not resize? **/ /*if (rows_extra < 0) @@ -814,7 +816,7 @@ aposInitiallizeGrid(pAposGrid theGrid) *** lines for each visual child. Searches nonvisual containers recursively for *** qualifying grandchildren. Floating windows are ignored. *** - *** Scrollpanes recieve only 2 vertical lines (skipping their horizontal edges), + *** Scrollpanes receive only 2 vertical lines (skipping their horizontal edges), *** and if the parent node is a scrollpane, the horizontal border lines are also *** skipped. *** @@ -836,7 +838,7 @@ pAposLine CurrLine, PrevLine; pXArray FirstCross, LastCross; /** Check if this node a scrollbar or window that needs an offset. **/ - aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL); + aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL, NULL); /** Does this widget need more room than it was given? **/ if (Parent->pre_width < Parent->min_width && Parent->min_width != 0) @@ -916,7 +918,7 @@ pXArray FirstCross, LastCross; /*** Adds 4 lines for the edges of each visual child. Searches nonvisual *** containers recursively for qualifying grandchildren. Floating windows - *** are ignored. Scrollpanes recieve only 2 vertical lines (skipping their + *** are ignored. Scrollpanes receive only 2 vertical lines (skipping their *** horizontal edges). *** *** @param Parent The parent who's children are being given lines. @@ -928,7 +930,7 @@ int aposAddLinesForChildren(pWgtrNode Parent, pXArray HLines, pXArray VLines) { int i=0, childCount=xaCount(&(Parent->Children)); -int isTopTab=0, isSideTab=0, tabWidth=0; +int isTopTab=0, isSideTab=0, tabWidth=0, tabHeight=0; int height_adj, width_adj; pWgtrNode C; @@ -943,7 +945,7 @@ pWgtrNode C; for(i=0; iChildren), i); - aposSetOffsetBools(C, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); + aposSetOffsetBools(C, NULL, NULL, &isTopTab, &isSideTab, &tabWidth, &tabHeight); /** Does this widget need more room than it was given? **/ height_adj = width_adj = 0; @@ -971,7 +973,7 @@ pWgtrNode C; *** start line and the bottom line is the end line *** because Y increases as we decend the page. ***/ - int minY = (C->y), maxY = (C->y + C->height + isTopTab*24); + int minY = (C->y), maxY = (C->y + C->height + isTopTab*tabHeight); if(aposCreateLine(C, HLines, minY, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_HORIZONTAL) < 0) goto CreateLineError; if(aposCreateLine(C, HLines, maxY, APOS_EWIDGETS, APOS_NOT_BORDER, height_adj, APOS_HORIZONTAL) < 0) @@ -1051,7 +1053,7 @@ pAposLine Line = aposExistingLine(Lines, Loc); return -1; } - /** Initiallize the new line. **/ + /** Initialize the new line. **/ memset(Line, 0, sizeof(AposLine)); xaInit(&(Line->SWidgets),16); xaInit(&(Line->EWidgets),16); @@ -1114,7 +1116,7 @@ int i, count = xaCount(Lines); } /*** Detects if a widget in PrevList (usually the widgets that started in or - *** crossed the pevious line) ends on this line (aka. appears in EWidgets). + *** crossed the previous line) ends on this line (aka. appears in EWidgets). *** If it does not end on this line, we know it crosses this line, so we add *** the widget to CWidgets. *** @@ -1160,7 +1162,7 @@ int found=0, i=0, j=0, pCount=xaCount(PrevList), eCount=xaCount(EWidgets); /*** Adds row and column sections to the grid based on the lines. *** - *** @param theGrid The grid to which sections shoudl be added. + *** @param theGrid The grid to which sections should be added. *** @param VDiff I had a hard time figuring out what this means. *** @param HDiff I had a hard time figuring out what this means. *** @returns 0 if successful, -1 otherwise. @@ -1240,7 +1242,7 @@ aposCreateSection(pXArray Sections, pAposLine StartL, pAposLine EndL, int Diff, { pAposSection NewSect; - /** Allocate and initiallize a new section. **/ + /** Allocate and initialize a new section. **/ if((NewSect = (pAposSection)(nmMalloc(sizeof(AposSection)))) < 0) { mssError(1, "APOS", "nmMalloc(): Couldn't allocate memory for new row or column"); @@ -1282,7 +1284,7 @@ pAposSection NewSect; *** *** @param StartL The line starting the section. (I think this is always the left/top.) *** @param EndL The line starting the section. (I think this is always the right/bottom.) - *** @param type Whether the section is a row (APOS_ROW) or a column (APAS_COL). + *** @param type Whether the section is a row (APOS_ROW) or a column (APOS_COL). *** @param isBorder Whether the section is on the border of the page. *** @returns 0 if successful, -1 otherwise. ***/ @@ -1330,10 +1332,10 @@ int eCount=xaCount(&(StartL->EWidgets)); } /*** Checks for any widgets starting on or crossing a line that are non-flexible - *** in the relevant dimention. + *** in the relevant dimension. *** *** @param L The line along which to check. - *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. *** @returns 1 if any child widget is non-flexible in the relevant dimension, *** 0 otherwise. ***/ @@ -1372,7 +1374,7 @@ int cCount = xaCount(&(L->CWidgets)); /*** Calculates the average flexibility of widgets on a line. *** *** @param L The line along which to check. - *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. *** @returns The average flexibility of children on the line. ***/ int @@ -1405,7 +1407,7 @@ int cCount = xaCount(&(L->CWidgets)); /*** Calculates the minimum flexibility of widgets on a line. *** *** @param L The line along which to check. - *** @param type Specifies the relevant dimetion using APOS_ROW or APOS_COL. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. *** @returns The minimum flexibility of children on the line. ***/ int @@ -1450,8 +1452,8 @@ int cCount = xaCount(&(L->CWidgets)); /*** Distributes extra or missing space among grid lines based on section flexibility. *** *** @param Lines The array of lines in the relevant direction on this grid. - *** @param Secctions The array of sections in the relevant direction on this grid. - *** @param Diff The space differencce from how the elements are currently spaced. + *** @param Sections The array of sections in the relevant direction on this grid. + *** @param Diff The space difference from how the elements are currently spaced. *** @returns The remaining space difference after spacing out elements as much as possible. ***/ int @@ -1474,8 +1476,8 @@ float TotalSum=0; TotalFlex += CurrSect->Flex; if(CurrSect->Flex) { - FlexibleSections++; - TotalFlexibleSpace += CurrSect->Width; + FlexibleSections++; + TotalFlexibleSpace += CurrSect->Width; } } @@ -1576,19 +1578,23 @@ float TotalSum=0; return Extra; } +/** TODO: Israel - Update widget flex scales to be 0 if design geometry is specified. **/ /*** Adjusts widget positions and sizes to snap them to grid lines. This *** function should be called after updating grid lines to ensure that *** widgets properly reflect the changes. *** *** @param Lines The lines being updated. *** @param flag Either APOS_ROW or APOS_COL. + *** @param info Info about the page design, currently used to determine if + *** flexibility should be used. *** @returns 0, success. ***/ int -aposSnapWidgetsToGrid(pXArray Lines, int flag) +aposSnapWidgetsToGrid(pXArray Lines, int flag, pWgtrClientInfo info) { +const int is_design = info->IsDesign; int i=0, j=0, count=0, lineCount = xaCount(Lines); -int isTopTab=0, isSideTab=0, tabWidth=0; +int isTopTab=0, isSideTab=0, tabWidth=0, tabHeight=0; int newsize; pAposLine CurrLine; pWgtrNode Widget; @@ -1602,14 +1608,16 @@ pWgtrNode Widget; for(j=0; jSWidgets), j); - if(flag == APOS_ROW) { - Widget->y = CurrLine->Loc; - Widget->total_fl_y = CurrLine->loc_fl; - } - else { - Widget->x = CurrLine->Loc; - Widget->total_fl_x = CurrLine->loc_fl; - } + if(flag == APOS_ROW) + { + Widget->y = CurrLine->Loc; + Widget->fl_scale_y = (is_design) ? 0.0 : CurrLine->loc_fl; + } + else + { + Widget->x = CurrLine->Loc; + Widget->fl_scale_x = (is_design) ? 0.0 : CurrLine->loc_fl; + } } /** Adjusts width or height of widgets ending on this line. **/ @@ -1617,7 +1625,7 @@ pWgtrNode Widget; for(j=0; jEWidgets), j); - aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); + aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth, &tabHeight); if(flag==APOS_ROW && Widget->fl_height) { /** Calculate the new size, taking APOS_MINWIDTH into account. **/ @@ -1629,13 +1637,11 @@ pWgtrNode Widget; else /*Widget->height = APOS_MINWIDTH;*/ Widget->height = Widget->pre_height; - + /*** The widget copies the adjustment weight of the - *** line, ignoring APOS_MINWIDTH. This might lead - *** to problems down the road, but I plan to fix - *** them if and when I encounter them. + *** line, ignoring APOS_MINWIDTH. ***/ - Widget->total_fl_h += CurrLine->my_fl; + Widget->fl_scale_h += (is_design) ? 0.0 : CurrLine->my_fl; } else if(flag==APOS_COL && Widget->fl_width) { @@ -1654,24 +1660,24 @@ pWgtrNode Widget; Widget->width = Widget->pre_width; /*** The widget copies the adjustment weight of the - *** line, ignoring APOS_MINWIDTH. This might lead - *** to problems down the road, but I plan to fix - *** them if and when I encounter them. + *** line, ignoring APOS_MINWIDTH. ***/ - Widget->total_fl_w += CurrLine->my_fl; + Widget->fl_scale_w += (is_design) ? 0.0 : CurrLine->my_fl; } } - /** Adjusts width or height of widgets ending on this line. **/ - count = xaCount(&(CurrLine->CWidgets)); - // printf("Doing %d widgets.\n", count); - for(j=0; jCWidgets), j); - if(flag==APOS_ROW && Widget->fl_height) - Widget->total_fl_h += CurrLine->my_fl; - else if(flag==APOS_COL && Widget->fl_width) - Widget->total_fl_w += CurrLine->my_fl; + if (!is_design) + { + /** Adjusts width or height of widgets ending on this line. **/ + count = xaCount(&(CurrLine->CWidgets)); + for(j=0; jCWidgets), j); + if(flag==APOS_ROW && Widget->fl_height) + Widget->fl_scale_h += (is_design) ? 0.0 : CurrLine->my_fl; + else if(flag==APOS_COL && Widget->fl_width) + Widget->fl_scale_w += (is_design) ? 0.0 : CurrLine->my_fl; + } } } @@ -1704,7 +1710,7 @@ int ival; return -1; } - aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL); + aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL, NULL); /**loop through children and process any windows**/ for(i=0; i Date: Fri, 6 Feb 2026 12:47:42 -0700 Subject: [PATCH 020/101] Improve error handling. Add an error message when cxsecVerifySymbol_n() fails. Improve an existing error message when htr_internal_WriteWgtrProperty() fails to write a property of an unknown type. Set Centrallix event listener to be explicitly non-passive, fixing a console error when later code assumes that calling preventDefault() is allowed. --- centrallix-lib/src/cxsec.c | 11 +++++++---- centrallix-os/sys/js/ht_render.js | 2 +- centrallix/htmlgen/ht_render.c | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/centrallix-lib/src/cxsec.c b/centrallix-lib/src/cxsec.c index e07980d6d..45890fb89 100644 --- a/centrallix-lib/src/cxsec.c +++ b/centrallix-lib/src/cxsec.c @@ -147,7 +147,7 @@ cxsecVerifySymbol_n(const char* sym, size_t n) ** significant security risks in the event of a locale mismatch!! **/ if (n <= 0 || (*sym != '_' && (*sym < 'A' || *sym > 'Z') && (*sym < 'a' || *sym > 'z'))) - return -1; + goto err; n--; /** Next chars may be 1) end of string, 2) digits, 3) alpha, or 4) underscore **/ @@ -155,11 +155,14 @@ cxsecVerifySymbol_n(const char* sym, size_t n) while(n) { if (*sym != '_' && (*sym < 'A' || *sym > 'Z') && (*sym < 'a' || *sym > 'z') && (*sym < '0' || *sym > '9')) - return -1; + goto err; sym++; n--; } - return 0; - } + return 0; + err: + fprintf(stderr, "WARNING: '%s' (of length %lu) is not a valid symbol!\n", sym, n); + return -1; + } diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index cc93e167a..6b80b9ace 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -1489,7 +1489,7 @@ function htr_addeventlistener(eventType,obj,handler) if (typeof pg_capturedevents[eventType] == 'undefined') { pg_capturedevents[eventType] = handler; - obj.addEventListener(eventType, handler, true); + obj.addEventListener(eventType, handler, { capture: true, passive: false }); } } else diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 647374a67..e0ec2305a 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1425,7 +1425,8 @@ htr_internal_WriteWgtrProperty(pHtSession s, pWgtrNode tree, char* propname) break; default: - htrAddScriptWgtr_va(s, "%STR&SYM:'Unknown Datatype (%INT) - Add it in ht_render.c:htr_internal_WriteWgtrProperty()', ", propname, t); + fprintf(stderr, "Failed to write widget property '%s': Unknown datatype %d (at %s:%d).\n", propname, t, __FILE__, __LINE__); + htrAddScriptWgtr_va(s, "%STR&SYM:'Unknown Datatype (%INT) - Add it to htr_internal_WriteWgtrProperty() in ht_render.c.', ", propname, t); break; } } From 799c20d6d16ba48816852448e468046daa444197 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 12:49:50 -0700 Subject: [PATCH 021/101] Fix the qprintf() % bug. Fix a bug in qprintf() that caused % and & characters inside conditional printing areas to always print, regardless of the condition. Improve documentation for qpfPrintf_va_internal() and qpf_grow_fn_t(). Clean up. --- centrallix-lib/include/qprintf.h | 9 +++++++++ centrallix-lib/src/qprintf.c | 22 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/centrallix-lib/include/qprintf.h b/centrallix-lib/include/qprintf.h index d638ef684..c7edbf2fc 100644 --- a/centrallix-lib/include/qprintf.h +++ b/centrallix-lib/include/qprintf.h @@ -32,6 +32,15 @@ #include +/*** A function to grow a string buffer. + *** + *** @param str The string buffer being grown. + *** @param size A pointer to the current size of the string buffer. + *** @param offset An offset up to which data must be preserved. + *** @param args Arguments for growing the buffer. + *** @param req The requested size. + ***/ +// typedef int (*qpf_grow_fn_t)(char** str, size_t* size, size_t offset, void* args, size_t req); typedef int (*qpf_grow_fn_t)(char**, size_t*, size_t, void*, size_t); typedef struct _QPS diff --git a/centrallix-lib/src/qprintf.c b/centrallix-lib/src/qprintf.c index 1f4538a3d..3bac70867 100644 --- a/centrallix-lib/src/qprintf.c +++ b/centrallix-lib/src/qprintf.c @@ -880,6 +880,14 @@ qpf_internal_Translate(pQPSession s, const char* srcbuf, size_t srcsize, char** *** change out from under this function to a new buffer if a realloc is *** done by the grow_fn function. Do not store pointers to 'str'. Go *** solely by offsets. + *** + *** NULL, &(s->Tmpbuf), &(s->TmpbufSize), htr_internal_GrowFn, (void*)s, fmt, va + *** @param s Optional session struct. + *** @param str Pointer to a string buffer where data will be written. + *** @param size Pointer to the current size of the string buffer. + *** @param grow_fn A function to grow the string buffer. + *** @param format The format of data which should be written. + *** @param ap The arguments list to fulfill the provided format. ***/ int qpfPrintf_va_internal(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, va_list ap) @@ -975,6 +983,12 @@ qpfPrintf_va_internal(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow /** Simple specifiers **/ if (__builtin_expect(format[0] == '%', 0)) { + if (ignore) + { + format++; + continue; + } + if (__builtin_expect(!nogrow, 1) && (__builtin_expect(cpoffset+2 <= *size, 1) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+2)))) (*str)[cpoffset++] = '%'; else @@ -987,6 +1001,12 @@ qpfPrintf_va_internal(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow } else if (__builtin_expect(format[0] == '&',0)) { + if (ignore) + { + format++; + continue; + } + if (__builtin_expect(!nogrow, 1) && (__builtin_expect(cpoffset+2 <= *size, 1) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+2)))) (*str)[cpoffset++] = '&'; else @@ -1448,5 +1468,3 @@ qpfRegisterExt(char* ext_spec, int (*ext_fn)(), int is_source) return; } - - From 00d6366980fbe525fa111760eb1e1d5cf91971c5 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:17:54 -0700 Subject: [PATCH 022/101] Improve naming conventions and dev experience. Add shortcut functions: ht_flex_x(), ht_flex_y(), ht_flex_w(), ht_flex_h(). Rename fl_scale_x (was total_fl_x). Rename fl_scale_y (was total_fl_y). Rename fl_scale_w (was total_fl_w). Rename fl_scale_h (was total_fl_h). Rename fl_parent_w (was parent_w). Rename fl_parent_h (was parent_h). Remove ht_flex_format_all and ht_flex_all(). Improve usage of new feature in previously updated widgets. Fix spelling mistakes. Clean up. --- centrallix/htmlgen/ht_render.c | 48 +++++----- centrallix/htmlgen/htdrv_autolayout.c | 8 +- centrallix/htmlgen/htdrv_image.c | 125 +++++++++++++------------- centrallix/htmlgen/htdrv_label.c | 27 +++--- centrallix/htmlgen/htdrv_pane.c | 37 ++++---- centrallix/htmlgen/htdrv_textbutton.c | 114 ++++++++++++++++------- centrallix/htmlgen/htdrv_treeview.c | 49 +++++----- centrallix/include/ht_render.h | 42 +++------ centrallix/include/wgtr.h | 6 +- centrallix/wgtr/wgtr.c | 44 +++++---- 10 files changed, 267 insertions(+), 233 deletions(-) diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index e0ec2305a..871eb258d 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1501,12 +1501,12 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) htr_internal_WriteWgtrProperty(s, tree, "fl_y"); htr_internal_WriteWgtrProperty(s, tree, "fl_width"); htr_internal_WriteWgtrProperty(s, tree, "fl_height"); - htr_internal_WriteWgtrProperty(s, tree, "total_fl_x"); - htr_internal_WriteWgtrProperty(s, tree, "total_fl_y"); - htr_internal_WriteWgtrProperty(s, tree, "total_fl_w"); - htr_internal_WriteWgtrProperty(s, tree, "total_fl_h"); - htr_internal_WriteWgtrProperty(s, tree, "parent_w"); - htr_internal_WriteWgtrProperty(s, tree, "parent_h"); + htr_internal_WriteWgtrProperty(s, tree, "fl_scale_x"); + htr_internal_WriteWgtrProperty(s, tree, "fl_scale_y"); + htr_internal_WriteWgtrProperty(s, tree, "fl_scale_w"); + htr_internal_WriteWgtrProperty(s, tree, "fl_scale_h"); + htr_internal_WriteWgtrProperty(s, tree, "fl_parent_w"); + htr_internal_WriteWgtrProperty(s, tree, "fl_parent_h"); } propname = wgtrFirstPropertyName(tree); while(propname) @@ -2723,10 +2723,10 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y "%[%STR %]" "}\n", id, - ht_flex(x, ht_get_total_w(node), ht_get_fl_x(node)), - ht_flex(y, ht_get_total_h(node), ht_get_fl_y(node)), - (w > 0), ht_flex(w, ht_get_total_w(node), ht_get_fl_w(node)), - (h > 0), ht_flex(h, ht_get_total_h(node), ht_get_fl_h(node)), + ht_flex_x(x, node), + ht_flex_y(y, node), + (w > 0), ht_flex_w(w, node), + (h > 0), ht_flex_h(h, node), (z > 0), z, (*textcolor), textcolor, (!strcmp(style, "bold")), @@ -2750,9 +2750,9 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y int -ht_get_total_w__INTERNAL(pWgtrNode widget) { +ht_get_parent_w__INTERNAL(pWgtrNode widget) { /** Check to see if the value was already cached by a previous call. **/ - int cached_value = widget->parent_w; + int cached_value = widget->fl_parent_w; if (cached_value != -1) { // printf( // "Got total width available to '%s' (%s) from cache: %dpx\n", @@ -2763,7 +2763,7 @@ ht_get_total_w__INTERNAL(pWgtrNode widget) { // DEBUG if (widget->Parent == NULL) { - printf("\nPANIC: Call to ht_get_total_w__INTERNAL() on widget with no parent!\n\n"); + printf("\nPANIC: Call to ht_get_parent_w__INTERNAL() on widget with no parent!\n\n"); wgtrPrint(widget, 1); } @@ -2779,21 +2779,21 @@ ht_get_total_w__INTERNAL(pWgtrNode widget) { /** Check if the parent has a width value. **/ if (parentWidth >= 0 /* && isParentVisual */) { int offset = parent->left + parent->right, ret = parentWidth - offset; - printf("Returning %d-%d=%d\n", parentWidth, offset, ret); - return (widget->parent_w = ret); + // printf("Returning %d-%d=%d\n", parentWidth, offset, ret); + return (widget->fl_parent_w = ret); } else { if (parent->Parent == NULL) { printf("Recursive call would segfault! Guessing %dpx instead.\n", parentWidth); - return (widget->parent_w = parentWidth); + return (widget->fl_parent_w = parentWidth); } - return (widget->parent_w = ht_get_total_w(parent)); + return (widget->fl_parent_w = ht_get_parent_w(parent)); } } int -ht_get_total_h__INTERNAL(pWgtrNode widget) { +ht_get_parent_h__INTERNAL(pWgtrNode widget) { /** Check to see if the value was already cached by a previous call. **/ - int cached_value = widget->parent_h; + int cached_value = widget->fl_parent_h; if (cached_value != -1) { // printf( // "Got total height available to '%s' (%s) from cache: %dpx\n", @@ -2804,7 +2804,7 @@ ht_get_total_h__INTERNAL(pWgtrNode widget) { // DEBUG if (widget->Parent == NULL) { - printf("\nPANIC: Call to ht_get_total_h__INTERNAL() on widget with no parent!\n\n"); + printf("\nPANIC: Call to ht_get_parent_h__INTERNAL() on widget with no parent!\n\n"); wgtrPrint(widget, 1); } @@ -2820,13 +2820,13 @@ ht_get_total_h__INTERNAL(pWgtrNode widget) { /** Check if the parent has a height value. **/ if (parentHeight >= 0 /* && isParentVisual */) { int offset = parent->top + parent->bottom, ret = parentHeight - offset; - printf("Returning %d-%d=%d\n", parentHeight, offset, ret); - return (widget->parent_h = ret); + // printf("Returning %d-%d=%d\n", parentHeight, offset, ret); + return (widget->fl_parent_h = ret); } else { if (parent->Parent == NULL) { printf("Recursive call would segfault! Guessing %dpx instead.\n", parentHeight); - return (widget->parent_h = parentHeight); + return (widget->fl_parent_h = parentHeight); } - return (widget->parent_h = ht_get_total_h__INTERNAL(parent)); + return (widget->fl_parent_h = ht_get_parent_h__INTERNAL(parent)); } } diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index 9cc7eb7c4..02d6d7d2a 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -111,10 +111,10 @@ htalRender(pHtSession s, pWgtrNode tree, int z) "Z-INDEX:%POS; " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), z ); diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index c46c66219..b9fe1585f 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -75,9 +75,9 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) char src[256]; int x=-1,y=-1,w,h; int id, i; - char *text; char fieldname[HT_FIELDNAME_SIZE]; char form[64]; + char* alt_text; char* aspect; if(!(s->Capabilities.Dom0NS || s->Capabilities.Dom1HTML)) @@ -104,9 +104,9 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) } if(wgtrGetPropertyValue(tree,"text",DATA_T_STRING,POD(&ptr)) == 0) - text=nmSysStrdup(ptr); + alt_text=nmSysStrdup(ptr); else - text=nmSysStrdup(""); + alt_text=nmSysStrdup(""); /** Image aspect scaling: stretch or preserve **/ if(wgtrGetPropertyValue(tree,"aspect",DATA_T_STRING,POD(&ptr)) == 0) @@ -122,7 +122,7 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) /*if (!htrCheckAddExpression(s, tree, name, "source") && wgtrGetPropertyValue(tree,"source",DATA_T_STRING,POD(&ptr)) != 0) { mssError(1,"HTIMG","Image widget must have a 'source' property"); - nmSysFree(text); + nmSysFree(alt_text); return -1; }*/ ptr = ""; @@ -144,7 +144,28 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) else form[0]='\0'; - /** Ok, write the style header items. **/ + /** Initialize linkage. **/ + htrAddWgtrObjLinkage_va(s, tree, "img%POS", id); + + /** Initialize image scripts. **/ + htrAddScriptInit_va(s, + "im_init(" + "wgtrGetNodeRef(ns,'%STR&SYM'), " + "{field:'%STR&JSSTR', form:'%STR&JSSTR'}" + ");\n", + name, + fieldname, form + ); + htrAddScriptInclude(s, "/sys/js/htdrv_image.js", 0); + + /** Event Handlers **/ + htrAddEventHandlerFunction(s, "document","MOUSEUP", "img", "im_mouseup"); + htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "img", "im_mousedown"); + htrAddEventHandlerFunction(s, "document","MOUSEOVER", "img", "im_mouseover"); + htrAddEventHandlerFunction(s, "document","MOUSEOUT", "img", "im_mouseout"); + htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "img", "im_mousemove"); + + /** Write the style for the image container div. **/ htrAddStylesheetItem_va(s, "\t#img%POS { " "left:"ht_flex_format"; " @@ -155,70 +176,52 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) "text-align:center; " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), z ); - /** Init image widget (?) **/ - htrAddWgtrObjLinkage_va(s, tree, "img%POS",id); - htrAddScriptInit_va(s, " im_init(wgtrGetNodeRef(ns,'%STR&SYM'), {field:'%STR&JSSTR', form:'%STR&JSSTR'});\n", - name, fieldname, form); - htrAddScriptInclude(s, "/sys/js/htdrv_image.js", 0); - - /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "img", "im_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "img", "im_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "img", "im_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "img", "im_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "img", "im_mousemove"); - - /** HTML body
element for the base layer. **/ - if (!strcmp(aspect, "stretch")) - { - htrAddBodyItemLayer_va(s, - 0, "img%POS", id, "wimage", - "\n\n", - id, w, h, src); - } - else // "preserve" - { - htrAddBodyItemLayer_va(s, - 0, "img%POS", id, "wimage", - "\n\n", - id, w, h, src); - } + /** Use a style that honors the aspect ratio attribute. **/ + char* style = (strcmp(aspect, "stretch") == 0) + ? /* "stretch" */ + "width:100%; " + "height:100%; " + : /* "preserve" */ + "width:100%; " + "height:auto; " + "max-width:fit-content; " + "max-height:fit-content; " + "display:inline; "; + + /** Write image HTML, including the containing div. **/ + htrAddBodyItemLayer_va(s, 0, + "img%POS", id, "wimage", + "\n\n", + id, + w, + h, + style, + src, + alt_text + ); /** Check for more sub-widgets **/ for (i=0;iChildren));i++) htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); - nmSysFree(text); + /** Clean up. **/ + nmSysFree(alt_text); + nmSysFree(aspect); return 0; } diff --git a/centrallix/htmlgen/htdrv_label.c b/centrallix/htmlgen/htdrv_label.c index ea315a9a5..77ea3ffdc 100644 --- a/centrallix/htmlgen/htdrv_label.c +++ b/centrallix/htmlgen/htdrv_label.c @@ -199,13 +199,13 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ htrAddStylesheetItem_va(s, "\t#lbl%POS { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "%[HEIGHT:"ht_flex_format"; %]" - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "z-index:%POS; " "cursor:default; " "%[font-weight:bold; %]" "%[color:%STR&CSSVAL; %]" @@ -217,10 +217,10 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) "%[font-style:italic; %]" "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - (!auto_height), ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + (!auto_height), ht_flex_h(h, tree), z, (is_bold), (*fgcolor), fgcolor, @@ -244,12 +244,11 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) "padding:0px; " "margin:0px; " "border-spacing:0px; " - "width:"ht_flex_format"; " + "width:100%%; " "}\n", id, align, - !strcmp(valign, "middle"), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)) + (strcmp(valign, "middle") == 0) ); htrAddWgtrObjLinkage_va(s, tree, "lbl%POS",id); diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 9bd8a6759..6536a9689 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -135,6 +135,8 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) if (!strcmp(ptr,"flat")) style = 2; if (!strcmp(ptr,"bordered")) style = 3; } + + /** Computes styling colors. **/ if (style == 1) /* raised */ { c1 = "white"; @@ -153,7 +155,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) strtcpy(bdr,ptr,sizeof(bdr)); } - /** Ok, write the style header items. **/ + /** Write the CSS for borders on the pane dom node. **/ int offset = 0; if (style == 2) { /* flat, the default style, nothing to do */ } else if (style == 0 || style == 1) /* lowered or raised */ @@ -183,24 +185,29 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) ); } + /** Apply the offset to the width and height. **/ + w += offset; + h += offset; + + /** Write the main CSS for the pane DOM node. **/ htrAddStylesheetItem_va(s, "\t#pn%POSmain {" - "POSITION:absolute; " - "VISIBILITY:inherit; " - "OVERFLOW:hidden; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "HEIGHT:"ht_flex_format"; " - "Z-INDEX:%POS; " - "border-radius: %INTpx;" - "%STR" + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "border-radius:%INTpx; " + "%STR " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w + offset, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h + offset, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), z, border_radius, main_bg diff --git a/centrallix/htmlgen/htdrv_textbutton.c b/centrallix/htmlgen/htdrv_textbutton.c index 92c07357b..66b7e0408 100644 --- a/centrallix/htmlgen/htdrv_textbutton.c +++ b/centrallix/htmlgen/htdrv_textbutton.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "ht_render.h" #include "obj.h" #include "cxlib/mtask.h" @@ -75,6 +76,7 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) char border_color[64]; char image_position[16]; /* top, left, right, bottom */ char image[OBJSYS_MAX_PATH]; + bool has_image; char h_align[16]; int image_width=0, image_height=0, image_margin=0; @@ -137,9 +139,15 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) /** Image source **/ if (wgtrGetPropertyValue(tree,"image",DATA_T_STRING,POD(&ptr)) != 0) + { strcpy(image, ""); + has_image = false; + } else + { strtcpy(image, ptr, sizeof(image)); + has_image = true; + } /** Image sizing **/ if (wgtrGetPropertyValue(tree,"image_width",DATA_T_INTEGER, POD(&image_width)) != 0) @@ -205,33 +213,39 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) htrAddScriptInclude(s, "/sys/js/htdrv_textbutton.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - /** Initial CSS styles **/ + /** Calculate size adjustment. **/ + const int adj = (2 * box_offset) + 1; + + /** Write CSS for the container that will hold the button. **/ htrAddStylesheetItem_va(s, "\t#tb%POSpane { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "%[HEIGHT:"ht_flex_format"; %]" - "Z-INDEX:%POS; " - "OVERFLOW:hidden; " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "z-index:%POS; " + "overflow:hidden; " "display:table; " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w-1-2*box_offset, ht_get_total_w(tree), ht_get_fl_w(tree)), - (h>=0), ht_flex(h-1-2*box_offset, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w - adj, tree), + (h >= 0), ht_flex_h(h - adj, tree), z ); - // CSS button click animation (replaces manual the JS implementation). + + /** Button click animation. **/ if (is_enabled) { htrAddStylesheetItem_va(s, "\t#tb%POSpane:active { transform: translate(1px, 1px); }\n", id ); } + + /** Write CSS for the button content, inside the border. **/ htrAddStylesheetItem_va(s, "\t#tb%POSpane .cell { " "height:100%%; " @@ -260,14 +274,20 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) bgstyle ); - /** CSS for image on the button **/ - if (image[0] && (image_width || image_height || image_margin)) + /** Write CSS for image on the button. **/ + if (has_image && (image_width != 0 || image_height != 0 || image_margin != 0)) { - htrAddStylesheetItem_va(s, "\t#tb%POSpane img { %[height:%POSpx; %]%[width:%POSpx; %]%[margin:%POSpx;%] }\n", - id, - image_height, image_height, - image_width, image_width, - image_margin, image_margin); + htrAddStylesheetItem_va(s, + "\t#tb%POSpane img { " + "%[height:%POSpx; %]" + "%[width:%POSpx; %]" + "%[margin:%POSpx; %]" + "}\n", + id, + (image_height != 0), image_height, + (image_width != 0), image_width, + (image_margin != 0), image_margin + ); } #if 00 @@ -291,30 +311,56 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) /** We need two DIVs here because of a long-outstanding Firefox bug :( **/ htrAddBodyItem_va(s, - "
" - "
" - "%[
%]" - "%[%]" + "
" + "
" + "%[
%]" + "%[%]" "%STR&HTE" - "%[%]" - "%[
%]" + "%[%]" + "%[
%]" "
" "
", id, - (image[0] && !strcmp(image_position, "top")), image, - (image[0] && !strcmp(image_position, "left")), image, + (has_image && strcmp(image_position, "top") == 0), image, + (has_image && strcmp(image_position, "left") == 0), image, text, - (image[0] && !strcmp(image_position, "right")), image, - (image[0] && !strcmp(image_position, "bottom")), image + (has_image && strcmp(image_position, "right") == 0), image, + (has_image && strcmp(image_position, "bottom") == 0), image ); /** Script initialization call. **/ //htrAddScriptInit_va(s, " tb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), span:document.getElementById(\"tb%POSspan\"), ena:%INT, c1:\"%STR&JSSTR\", c2:\"%STR&JSSTR\", dc1:\"%STR&JSSTR\", top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR'});\n", //name, id, is_enabled, fgcolor1, fgcolor2, disable_color, w, h, is_ts, name, text); - htrAddScriptInit_va(s, " tb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), ena:%INT, c1:\"%STR&JSSTR\", c2:\"%STR&JSSTR\", dc1:\"%STR&JSSTR\", top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR'});\n", - name, is_enabled, fgcolor1, fgcolor2, disable_color, w, h, is_ts, name, text); + htrAddScriptInit_va(s, + "tb_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "ena:%INT, " + "c1:'%STR&JSSTR', " + "c2:'%STR&JSSTR', " + "dc1:'%STR&JSSTR', " + "top:null, " + "bottom:null, " + "right:null, " + "left:null, " + "width:%INT, " + "height:%INT, " + "tristate:%INT, " + "name:'%STR&SYM', " + "text:'%STR&JSSTR', " + "});\n", + name, + is_enabled, + fgcolor1, + fgcolor2, + disable_color, + w, + h, + is_ts, + name, + text + ); - /** Add the event handling scripts **/ + /** Add event handlers. **/ htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tb", "tb_mousedown"); htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tb", "tb_mouseup"); htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tb", "tb_mouseover"); diff --git a/centrallix/htmlgen/htdrv_treeview.c b/centrallix/htmlgen/htdrv_treeview.c index 05a2bae8f..2e3e44cbe 100644 --- a/centrallix/htmlgen/htdrv_treeview.c +++ b/centrallix/htmlgen/htdrv_treeview.c @@ -174,18 +174,18 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s, "\t#tv%POSroot { " - "POSITION:absolute; " - "VISIBILITY:%STR; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:%STR; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, (show_root) ? "inherit" : "hidden", - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), z ); } @@ -221,32 +221,31 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) else { htrAddBodyItem_va(s, - "
" - "" + "" " %STR&HTE" - "
\n", - id, /** Class **/ - id, /** ID **/ + "
\n", + id, /* class */ + id, /* id */ (show_root) ? "inherit" : "hidden", - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), z, (*icon) ? icon : "/sys/images/ico02b.gif", src ); htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "tv%POSload", id, NULL, ""); - /*htrAddBodyItem_va(s, "
\n",id);*/ } /** Event handler for click-on-url **/ diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index 02d32ec6d..63247cfaf 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -390,43 +390,40 @@ int htruleRegister(char* ruletype, ...); *** *** @param size The original size of the ui element. *** @param total The total size of the ui element's container. - *** @param flex The flexibility of the ui element. It is strongly recomended + *** @param flex The flexibility of the ui element. It is strongly recommended *** to generate this with an ht_get_fl function call. *** @returns Several values to serve as parameters for a qprintf call. ***/ #define ht_flex(size, total, flex) (size), (total), (flex) /** ====[ Macros for getting total container size ]==== **/ -int ht_get_total_w__INTERNAL(pWgtrNode widget); -int ht_get_total_h__INTERNAL(pWgtrNode widget); +int ht_get_parent_w__INTERNAL(pWgtrNode widget); +int ht_get_parent_h__INTERNAL(pWgtrNode widget); -#define ht_get_total_w(widget) ht_get_total_w__INTERNAL(widget) -#define ht_get_total_h(widget) ht_get_total_h__INTERNAL(widget) - -// #define ht_get_total_w(widget) ((widget)->Parent->width - (widget)->Parent->left - (widget)->Parent->right) -// #define ht_get_total_h(widget) ((widget)->Parent->height - (widget)->Parent->top - (widget)->Parent->bottom) +#define ht_get_parent_w(widget) ht_get_parent_w__INTERNAL(widget) +#define ht_get_parent_h(widget) ht_get_parent_h__INTERNAL(widget) /** ====[ Macros for getting total flexibilities ]==== **/ /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the x direction. ***/ -#define ht_get_fl_x(widget) ((widget)->total_fl_x) +#define ht_get_fl_x(widget) ((widget)->fl_scale_x) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the y direction. ***/ -#define ht_get_fl_y(widget) ((widget)->total_fl_y) +#define ht_get_fl_y(widget) ((widget)->fl_scale_y) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the width direction. ***/ -#define ht_get_fl_w(widget) ((widget)->total_fl_w) +#define ht_get_fl_w(widget) ((widget)->fl_scale_w) /*** @param widget The widget to be queried. *** @returns The flexibility of the widget in the height direction. ***/ -#define ht_get_fl_h(widget) ((widget)->total_fl_h) +#define ht_get_fl_h(widget) ((widget)->fl_scale_h) /*** @brief A shortcut function to get the flexibility when writing the *** LEFT CSS attribute. @@ -444,22 +441,10 @@ int ht_get_total_h__INTERNAL(pWgtrNode widget); /** ====[ Macros for being lazy ]==== **/ -#define ht_flex_x(x, widget) ht_flex(x, ht_get_total_w(widget), ht_get_fl_x(widget)) -#define ht_flex_y(y, widget) ht_flex(y, ht_get_total_h(widget), ht_get_fl_y(widget)) -#define ht_flex_w(w, widget) ht_flex(w, ht_get_total_w(widget), ht_get_fl_w(widget)) -#define ht_flex_h(h, widget) ht_flex(h, ht_get_total_h(widget), ht_get_fl_h(widget)) - -#define ht_flex_format_all \ - "LEFT:"ht_flex_format"; " \ - "TOP:"ht_flex_format"; " \ - "WIDTH:"ht_flex_format"; " \ - "HEIGHT:"ht_flex_format"; " \ - -#define ht_flex_all(x, y, w, h, widget) \ - ht_flex_x(x, widget), \ - ht_flex_y(y, widget), \ - ht_flex_w(w, widget), \ - ht_flex_h(h, widget) \ +#define ht_flex_x(x, widget) ht_flex(x, ht_get_parent_w(widget), ht_get_fl_x(widget)) +#define ht_flex_y(y, widget) ht_flex(y, ht_get_parent_h(widget), ht_get_fl_y(widget)) +#define ht_flex_w(w, widget) ht_flex(w, ht_get_parent_w(widget), ht_get_fl_w(widget)) +#define ht_flex_h(h, widget) ht_flex(h, ht_get_parent_h(widget), ht_get_fl_h(widget)) // Workaround because -lm isn't passed to my editor and this was the easiest way to fix it. // This code should not appear in a pull request. If you are a code reviewer, remove this @@ -469,4 +454,3 @@ int ht_get_total_h__INTERNAL(pWgtrNode widget); #endif #endif /* _HT_RENDER_H */ - diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index d9a89b1dc..fd296ead3 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -103,9 +103,9 @@ typedef struct _WN int pre_x, pre_y, pre_width, pre_height; /** pre-layout geom. **/ int fl_x, fl_y, fl_width, fl_height;/** Flexibilities as specified by the designer **/ double fx, fy, fw, fh; /** internal flexibility calculations **/ - double total_fl_x, total_fl_y; /** Total flexiblities as calculated for this layout. */ - double total_fl_w, total_fl_h; /** Responsive CSS adjustment weights for width and height */ - int parent_w, parent_h; /** The expected size of the parent container */ + double fl_scale_x, fl_scale_y; /** Scaled x and y flexibilities calculated for this layout. */ + double fl_scale_w, fl_scale_h; /** Scaled w and h flexibilities calculated for this layout. */ + int fl_parent_w, fl_parent_h; /** The expected size of the parent container, used when it flexes. */ int min_width, min_height; /** absolute minimums **/ int x, y, width, height; /** actual geometry **/ int top, bottom, left, right; /** container offsets **/ diff --git a/centrallix/wgtr/wgtr.c b/centrallix/wgtr/wgtr.c index 5723c67eb..0f19ffc41 100755 --- a/centrallix/wgtr/wgtr.c +++ b/centrallix/wgtr/wgtr.c @@ -1306,17 +1306,18 @@ wgtrGetPropertyType(pWgtrNode widget, char* name) pObjProperty prop; ASSERTMAGIC(widget, MGK_WGTR); - if (!strcmp(name, "name")) return DATA_T_STRING; - else if (!strcmp(name, "outer_type")) return DATA_T_STRING; + if (!strcmp(name, "name") || !strcmp(name, "outer_type")) + return DATA_T_STRING; else if (!strcmp(name, "x") || !strcmp(name, "y") || !strcmp(name, "width") || !strcmp(name, "height") || !strcmp(name, "r_x") || !strcmp(name, "r_y") || !strcmp(name, "r_width") || !strcmp(name, "r_height") || !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height") || - !strcmp(name, "parent_w") || !strcmp(name, "parent_h")) + !strcmp(name, "fl_parent_w") || !strcmp(name, "fl_parent_h")) return DATA_T_INTEGER; - else if (!strcmp(name, "total_fl_x") || !strcmp(name, "total_fl_y") || - !strcmp(name, "total_fl_w") || !strcmp(name, "total_fl_h") || + else if (!strcmp(name, "fl_scale_x") || !strcmp(name, "fl_scale_y") || + !strcmp(name, "fl_scale_w") || !strcmp(name, "fl_scale_h") || !strcmp(name, "fx") || !strcmp(name, "fy") || !strcmp(name, "fw") || !strcmp(name, "fh")) return DATA_T_DOUBLE; + count = xaCount(&(widget->Properties)); for (i=0;iInteger = widget->fl_y; return 0; } if (!strcmp(name+3, "width")) { val->Integer = widget->fl_width; return 0; } if (!strcmp(name+3, "height")) { val->Integer = widget->fl_height; return 0; } - } - else if (!strncmp(name, "parent_", 7)) - { - if (!strcmp(name+7, "w")) { val->Integer = widget->parent_w; return 0; } - if (!strcmp(name+7, "h")) { val->Integer = widget->parent_h; return 0; } + if (!strcmp(name+3, "parent_w")) { val->Integer = widget->fl_parent_w; return 0; } + if (!strcmp(name+3, "parent_h")) { val->Integer = widget->fl_parent_h; return 0; } } } - if (datatype == DATA_T_DOUBLE) + else if (datatype == DATA_T_DOUBLE) { - if (!strncmp(name, "f", 1)) - { - if (!strcmp(name+1, "x")) { val->Double = widget->fx; return 0; } + if (!strncmp(name, "fl_scale_", 9)) + { + if (!strcmp(name+9, "x")) { val->Double = (double)widget->fl_scale_x; return 0; } + else if (!strcmp(name+9, "y")) { val->Double = (double)widget->fl_scale_y; return 0; } + else if (!strcmp(name+9, "w")) { val->Double = (double)widget->fl_scale_w; return 0; } + else if (!strcmp(name+9, "h")) { val->Double = (double)widget->fl_scale_h; return 0; } + } + else if (!strncmp(name, "f", 1)) + { + if (!strcmp(name+1, "x")) { val->Double = widget->fx; return 0; } else if (!strcmp(name+1, "y")) { val->Double = widget->fy; return 0; } else if (!strcmp(name+1, "w")) { val->Double = widget->fw; return 0; } else if (!strcmp(name+1, "h")) { val->Double = widget->fh; return 0; } } - else if (!strncmp(name, "total_fl_", 9)) - { - if (!strcmp(name+9, "x")) { val->Double = (double)widget->total_fl_x; return 0; } - else if (!strcmp(name+9, "y")) { val->Double = (double)widget->total_fl_y; return 0; } - else if (!strcmp(name+9, "w")) { val->Double = (double)widget->total_fl_w; return 0; } - else if (!strcmp(name+9, "h")) { val->Double = (double)widget->total_fl_h; return 0; } - } } else if (datatype == DATA_T_STRING) { @@ -1598,7 +1596,7 @@ wgtrNewNode( char* name, char* type, pObjSession s, node->fl_y = fly; node->fl_width = flwidth; node->fl_height = flheight; - node->parent_h = node->parent_w = -1; + node->fl_parent_h = node->fl_parent_w = -1; node->ObjSession = s; node->Parent = NULL; node->min_height = node->min_width = 0; @@ -2600,5 +2598,3 @@ wgtrGetNamespace(pWgtrNode widget) { return widget->Namespace; } - - From f414f1d7e81174415df214e9a156cf9914611163 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:18:09 -0700 Subject: [PATCH 023/101] Update testing apps. Clean up some apps. --- centrallix-os/samples/autoscale_test.app | 36 ++++++++++++++++-------- centrallix-os/tests/T02-Combined2.app | 7 ++--- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/centrallix-os/samples/autoscale_test.app b/centrallix-os/samples/autoscale_test.app index bcaace5c6..d5965395c 100644 --- a/centrallix-os/samples/autoscale_test.app +++ b/centrallix-os/samples/autoscale_test.app @@ -6,13 +6,13 @@ MyPage "widget/page" textcolor = "#00f8ff"; width = 1000; height = 1000; - + auto "widget/hbox" { x=100; y=50; width=900; height=750; spacing=20; row_height=300; fl_width=100; fl_height=100; - + pane0 "widget/pane" { fl_width=100; fl_height=100; width=190; height=180; bgcolor = "#9cf"; } // x=100; y=50; pane1 "widget/pane" { fl_width=100; fl_height=100; width=130; height=180; bgcolor = "#ccc"; } // x=305; y=50; pane2 "widget/pane" { fl_width=100; fl_height=100; width=80; height=320; bgcolor = "#f99"; } // x=455; y=50; @@ -24,48 +24,62 @@ MyPage "widget/page" pane8 "widget/pane" { fl_width=100; fl_height=100; width=110; height=200; bgcolor = "#fc9"; } // x=615; y=390; pane9 "widget/pane" { fl_width=100; fl_height=100; width=130; height=120; bgcolor = "#cf9"; } // x=745; y=390; } - + // pane0 "widget/pane" { x=100; y=50; width=190; height=180; bgcolor = "#9cf"; } // pane1 "widget/pane" { x=305; y=50; width=130; height=180; bgcolor = "#ccc"; } // pane2 "widget/pane" { x=455; y=50; width=80; height=320; bgcolor = "#f99"; } // pane3 "widget/pane" { x=555; y=50; width=150; height=140; bgcolor = "#9f9"; } - // pane4 "widget/pane" { x=725; y=50; width= 90; height=240; bgcolor = "#99f"; } + // pane4 "widget/pane" { x=725; y=50; width=90; height=240; bgcolor = "#99f"; } // pane5 "widget/pane" { x=80; y=390; width=230; height=100; bgcolor = "#ff9"; } // pane6 "widget/pane" { x=325; y=390; width=80; height=200; bgcolor = "#f9f"; } // pane7 "widget/pane" { x=425; y=390; width=170; height=220; bgcolor = "#9ff"; } // pane8 "widget/pane" { x=615; y=390; width=110; height=200; bgcolor = "#fc9"; } // pane9 "widget/pane" { x=745; y=390; width=130; height=120; bgcolor = "#cf9"; } - + paneA "widget/pane" { x=40; y=680; width=890; height=220; bgcolor = "#620"; } - + + b "widget/button" + { + x=80; y=700; + width=40; height=20; + + type = "text"; + text = "text button"; + bgcolor = "#aee"; + fgcolor1 = "white"; + fgcolor2 = "red"; + disable_color = "grey"; + enabled = yes; + } + // Outline the visible area. top_left0 "widget/pane" { x=0; y=0; width=10; height=10; bgcolor = "#f00"; } top_right0 "widget/pane" { x=990; y=0; width=10; height=10; bgcolor = "#ff0"; } bottom_left0 "widget/pane" { x=0; y=990; width=10; height=10; bgcolor = "#0f0"; } bottom_right0 "widget/pane" { x=990; y=990; width=10; height=10; bgcolor = "#00f"; } - + // Advance markers. top_left1 "widget/pane" { x=100; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#a00"; } top_right1 "widget/pane" { x=890; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#aa0"; } bottom_left1 "widget/pane" { x=100; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#0a0"; } bottom_right1 "widget/pane" { x=890; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#00a"; } - + // Interior markers. top_left2 "widget/pane" { x=250; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#700"; } top_right2 "widget/pane" { x=740; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#770"; } bottom_left2 "widget/pane" { x=250; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#070"; } bottom_right2 "widget/pane" { x=740; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#007"; } - + // Deep interior markers. top_left3 "widget/pane" { x=400; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#500"; } top_right3 "widget/pane" { x=590; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#550"; } bottom_left3 "widget/pane" { x=400; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#050"; } bottom_right3 "widget/pane" { x=590; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#005"; } - + // Center marker. center "widget/pane" { x=450; y=450; width=100; height=100; bgcolor = "orange"; centerer "widget/pane" { x=25; y=25; width=50; height=50; bgcolor = "purple"; } // Debug - } + } } \ No newline at end of file diff --git a/centrallix-os/tests/T02-Combined2.app b/centrallix-os/tests/T02-Combined2.app index 570387b6d..b60bc597f 100644 --- a/centrallix-os/tests/T02-Combined2.app +++ b/centrallix-os/tests/T02-Combined2.app @@ -112,15 +112,14 @@ Page1 "widget/page" style="lowered"; readonly="yes"; } - - } + TabPage2 "widget/tabpage" { Label1 "widget/label" { - x=10; y=10; width=100; height=15; - text="Current State:"; + x=10; y=15; width=100; height=15; + text="Current State:"; align="right"; } Editbox4 "widget/editbox" From 20a73e759ee3351950a178109e7c62f9bcafdf76 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:18:38 -0700 Subject: [PATCH 024/101] Add tools to ht_geom_dom1html. Add Math.clamp() and Math.isBetween(). Add getParentSize(), getParentW(), and getParentH(). Refactor getRelativeX/Y/W/H() to call the new getRelative(). Refactor setRelativeX/Y/W/H() to call the new setRelative(). Add fast_setRelativeX/Y(). Add setResponsiveX/Y/W/H() using a new shared setResponsive(). Add responsiveness to moveTo() with the new functions. Fix style guide mistakes. Clean up. --- centrallix-os/sys/js/ht_geom_dom1html.js | 232 ++++++++++++++++++++--- 1 file changed, 204 insertions(+), 28 deletions(-) diff --git a/centrallix-os/sys/js/ht_geom_dom1html.js b/centrallix-os/sys/js/ht_geom_dom1html.js index f137d5dba..71d29db99 100644 --- a/centrallix-os/sys/js/ht_geom_dom1html.js +++ b/centrallix-os/sys/js/ht_geom_dom1html.js @@ -11,34 +11,61 @@ // Cross browser Geometry DOM1HTML +// Add some useful functions to Math that will be needed elsewhere. +// Math.clamp = (min, val, max) => Math.min(Math.max(min, val), max); +// Math.isBetween = (lowerBound, num, upperBound) => lowerBound < num && num < upperBound; + +// Dev debug versions. +Math.clamp = (min, val, max) => + { + if (min > max || isNaN(min) || isNaN(val) || isNaN(max)) + { + console.warn(`Math.clamp(${min}, ${val}, ${max});`); + console.trace(); + } + return Math.min(Math.max(min, val), max); + } +Math.isBetween = (lowerBound, num, upperBound) => + { + if (lowerBound > upperBound || isNaN(lowerBound) || isNaN(num) || isNaN(upperBound)) + console.warn(`Math.isBetween(${lowerBound}, ${num}, ${upperBound});`); + return lowerBound < num && num < upperBound; + } + + + /*** Experimental system for turning off clipping CSS. *** The clip values are still stored and can be queried *** for legacy compatibility, but they will not output *** any clip rectangles or clip paths in the CSS or HTML. ***/ /** Ensure clipping is disabled for a layer / HTML node. **/ -function disableClippingCSS(l) { +function disableClippingCSS(l) + { console.log(`Turning off clipping for ${l.clip.obj.id}.`); l.clip.noclip = true; updateClippingCSS(l); -} + } /** Ensure clipping is enabled for a layer / HTML node. **/ -function enableClippingCSS(l) { +function enableClippingCSS(l) + { l.clip.noclip = false; updateClippingCSS(l); -} + } /** Update clipping without changing any specific values. **/ -function updateClippingCSS(l) { +function updateClippingCSS(l) + { setClipTop(l, getClipTop(l)); -} + } /** Debug function for finding clipped dom nodes. **/ -function getClipped() { +function getClipped() + { return Array - .from(Window.clipped) - .filter(id=>id) + .from(Window.clipped) + .filter(id=>id) .map(id=>document.getElementById(id)); -} + } // Clip Width function getClipWidth(l) @@ -236,43 +263,192 @@ function getpageYOffset() return window.pageYOffset; } -function getRelativeX(l) +/*** Get the size of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The width and height of the parent container. + ***/ +function getParentSize(l) + { + const parentRect = l.parentNode.getBoundingClientRect(); + return { width: parentRect.width, height: parentRect.height }; + } + +/*** Get the width of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The width of the parent container. + ***/ +function getParentW(l) + { + return getParentSize(l).width; + } + +/*** Get the height of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The height of the parent container. + ***/ +function getParentH(l) { - if (l.__pg_left != null) return l.__pg_left; - var left = parseInt(pg_get_style(l,'left')); - l.__pg_left = isNaN(left)?0:left; - return l.__pg_left; + return getParentSize(l).height; } -function setRelativeX(l, value) + +/*** Problem: + *** If the programmer calls setRelativeX() (or a similar function, such as moveTo() or moveBy()), + *** they might be using a value they got from the server, based on the resolution when the page + *** was first loaded. However, they might also be using a value they got dynamically by calling + *** some function to check the actual size of an element. Previously, this distinction did not + *** matter because these values would be the same. However, now that pages can be resized on the + *** client, it does matter. + ***/ + + +/*** We ignore the current value of __pg_left in the following functions even + *** though it might be correct and faster than querying the DOM. However, the + *** layout may have changed since last time, so we always requery the DOM. + ***/ +function getRelative(l, d) { - pg_set_style(l,'left',(l.__pg_left = parseInt(value))); - return l.__pg_left; + const val = parseInt(pg_get_style(l, d, NaN)); + return l['__pg_' + d] = (isNaN(val)) ? 0 : val; } -function getRelativeY(l) +function getRelativeX(l) { return getRelative(l, 'left'); } +function getRelativeY(l) { return getRelative(l, 'top'); } +function getRelativeW(l) { return getRelative(l, 'width'); } +function getRelativeH(l) { return getRelative(l, 'height'); } + +/*** Sets the location of a DOM node relative to its parent container. + *** + *** @param l The DOM node being set. (Assumed to be defined.) + *** @param value The new location. This can be a CSS string. + *** @param {'left'|'top'|'width'|'height'} d The dimension being set. + ***/ +function setRelative(l, value, d) { - if (l.__pg_top != null) return l.__pg_top; - return (l.__pg_top = parseInt(pg_get_style(l,'top'))); + /** Convert the value to a number, if possible. **/ + const parsedValue = parseInt(value); + if (!isNaN(parsedValue)) value = parsedValue; + + pg_set_style(l, d, value); + l['__pg_' + d + '_style'] = value; + return l['__pg_' + d] = parseInt(pg_get_style(l, d)); } -function setRelativeY(l, value) +function setRelativeX(l, value) { return setRelative(l, value, 'left'); } +function setRelativeY(l, value) { return setRelative(l, value, 'top'); } +function setRelativeW(l, value) { return setRelative(l, value, 'width'); } +function setRelativeH(l, value) { return setRelative(l, value, 'height'); } + +/*** Use these functions only in situations where performances is required + *** and you (a) value does not need to be parsed and (b) you do not need + *** the return value of the function. + ***/ +function fast_setRelativeX(l, value) { - pg_set_style(l,'top',(l.__pg_top = parseInt(value))); - return l.__pg_top; + pg_set_style(l, 'left', value); + l['__pg_x_style'] = value; } +function fast_setRelativeY(l, value) + { + pg_set_style(l, 'top', value); + l['__pg_y_style'] = value; + } + +/*** Sets a dimension of a DOM element using coordinates in the server + *** generated adaptive layout. It is RECOMMENDED to call a specific sub- + *** function (aka. setResponsiveX(), setResponsiveY(), etc.) instead of + *** calling this function directly to avoid passing dimension directly. + *** + *** WARNING: Ensure that any value passed is calculated ENTIRELY using + *** values from the server (e.g. widget properties) and no values from + *** real page dimensions are used, as these change when the page is + *** resized after being loaded for the first time. + *** + *** @param l The DOM node being set. (Assumed to be defined.) + *** @param value The new location in server-side px. This value must be + *** parseable as a number. + *** @param {'x'|'y'|'w'|'h'} d The letter for the dimension being set. + ***/ +function setResponsive(l, value, d) { + /** Convert the value to a number, if possible. **/ + const parsedValue = parseInt(value); + if (!isNaN(parsedValue)) value = parsedValue; + + /** Server-layout values are always numbers. **/ + if (typeof(value) !== 'number') + { + console.warn(`setResponsive(${l.id}, ?, '${d}'): Expected value to be a parseable number but got:`, value); + return value; + } + + /** The flexibility specified by the server. **/ + var fl_scale = l['__fl_scale_' + d] ?? wgtrGetServerProperty(l, 'fl_scale_' + d); + if (fl_scale == undefined || fl_scale == null) + { + /** The server did not specify a flexibility, even though one was expected. **/ + const warningMsg = 'setResponsive() - FAIL: Missing ' + ((wgtrIsNode(l)) ? 'wgtr.' : '__') + 'fl_scale_' + d; + console.warn(warningMsg, l); + fl_scale = 0; + } + + /** Inflexible elements don't need to be responsive. **/ + if (fl_scale <= 0) return setRelative(l, value, d); + + /** The parent width expected by the server in the adaptive layout. **/ + var d2 = d; + if (d2 == 'x') d2 = 'w'; + if (d2 == 'y') d2 = 'h'; + + var fl_parent = l['__fl_parent_' + d2] ?? wgtrGetServerProperty(l, 'fl_parent_' + d2); + if (fl_parent == undefined || fl_parent == null) + { + const warningMsg = 'setResponsive() - FAIL: Missing ' + ((wgtrIsNode(l)) ? 'wgtr.' : '__') + 'fl_parent_' + d2; + console.warn(warningMsg, l); + } + + /** Generate and set the CSS. **/ + const css = `calc(${value}px + (100% - ${fl_parent}px) * ${fl_scale})`; + const prop = { x:'left', y:'top', w:'width', h:'height' }[d]; + return setRelative(l, css, prop); +} +/** Call these functions instead of calling setResponsive() directly, which leads to less readable code. **/ +function setResponsiveX(l, value) { return setResponsive(l, value, 'x'); } +function setResponsiveY(l, value) { return setResponsive(l, value, 'y'); } +function setResponsiveW(l, value) { return setResponsive(l, value, 'w'); } +function setResponsiveH(l, value) { return setResponsive(l, value, 'h'); } + +/** Moves a DOM node to a location within the window. **/ function moveToAbsolute(l, x, y) { setPageX(l,x); setPageY(l,y); } -function moveTo(l, x, y) +/*** Moves a DOM node to a location inside it's parent container. + *** + *** @param l The DOM node being moved. + *** @param x The new x coordinate. Can be a CSS string (if responsive is false). + *** @param y The new y coordinate. Can be a CSS string (if responsive is false). + *** @param responsive Whether the given coordinates should be treated as + *** adaptive, 'server-side', coordinates where setResponsive() + *** should be invoked to give them responsive design. + ***/ +function moveTo(l, x, y, responsive = false) { - //pg_set_style_string(this,'position','absolute'); - setRelativeX(l,x); - setRelativeY(l,y); + if (responsive) + { + setResponsiveX(l, x); + setResponsiveY(l, x); + } + else + { + setRelativeX(l, x); + setRelativeY(l, y); + } } From 0f6f47a71a3e081585b3034f21911449ee7021ae Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:22:34 -0700 Subject: [PATCH 025/101] Add tools for widget development and debugging. Add support for more edge cases with undefined values to wgtrGetServerProperty(). Add the Log action (and docs). Add the ReloadPage action (and docs). Improve documentation for the Alert widget. --- centrallix-doc/Widgets/widgets.xml | 8934 +++++++++++++------------ centrallix-os/sys/js/ht_utils_wgtr.js | 2 +- centrallix-os/sys/js/htdrv_page.js | 12 + 3 files changed, 4482 insertions(+), 4466 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index b6b50afde..42b81a8f6 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -1,4465 +1,4469 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]> - - - - - - - -

The autolayout widget is used to automatically position widgets without having to explicitly provide x, y, width, and height values for those widgets. Most of the layout in Centrallix is done on a coordinate basis (although the automatic resize logic adjusts those coordinates to fit the target application or component render size). The autolayout widget provides the capability to do nested gui layout in Centrallix, where the coordinate positions of the widgets are computed automatically.

- -

Normally, the autolayout widget is used not as "widget/autolayout", but as "widget/hbox" or "widget/vbox", two synonyms for this widget which also set the layout direction (horizontal or vertical).

- -

A "hbox" widget stacks its children horizontally one after the other, while a "vbox" widget stacks its children vertically from the top down. The spacing between children can be set, and the size of each child can be either independently set per-child, or it can be set by virtue of the geometry of the autolayout widget.

- -

The autolayout widget supports having multiple rows or columns of widgets. That is, if a "vbox" widget fills up vertically, it can resume adding children to a second vertical column should there be space to the right of the original column of widgets. For this to work, the width of the columns (for a vbox) or the height of the rows (for an hbox) must be specified.

- -
- - - -

The autolayout widget can occur any place that a visual widget can be placed. As it is a container, it can also contain both visual and nonvisual widgets and containers.

- -
- - - - Sets the alignment of text can have left (default) right or center. - - For an hbox, this is the width to use for the child widgets (unless otherwise specified by the child widget). For a vbox, this refers to the height to use for child widgets unless otherwise specified. - - This specifies the width of columns for a vbox -- should the first column of widgets fill up, a second column will be started at this offset from the start of the first column. - - Height, in pixels, of the autolayout area. If omitted, it defaults to the maximum available height for the given width, without overlapping other visible widgets. If both width and height are unspecified, Centrallix will chose a width and height that maximize the available autolayout area. - - Makes the text fill page (justifies text - google it if you need). - - This specifies the height of rows for an hbox -- should the first row of widgets fill up horizontally, a second row will be started beneath the first one, at this vertical offset from the start of the first row. - - The spacing, in pixels, between widgets. For an hbox, this refers to the horizontal spacing between children. For a vbox, this refers to the vertical spacing. - - Either "hbox" or "vbox". Not needed if the widget is created via "widget/hbox" or "widget/vbox". - - X-coordinate of the upper left corner of the autolayout area. - - Y-coordinate of the upper left corner of the autolayout area. - - Width, in pixels, of the autolayout area. If omitted, it defaults to the maximum available width for the given height, without overlapping other visible widgets. - - - - - - - - An optional property, this allows the layout sequence of child widgets to be controlled, and the children are placed in ascending order. If unspecified, this defaults to a value of 100 for the first child encountered, or to N+1 where N is the autolayout sequence of the most recent child widget encountered. - - Height, in pixels, of the child. - - Width, in pixels, of the child. - - - - - - - - - - - - - -
- - - - - -

Combines the functionality of the textbutton and imagebutton, as well as adding the ability to have both text and image displayed in a button.

- -
- - - -

A Button can be placed inside any visible container, but only nonvisual widgets can be placed within it. Properties that don't apply to the button's type are ignored.

- -
- - - - A color, RGB or named, to be used as the button's background.If neither bgcolor nor background are specified, the button is transparent. - - The ObjectSystem pathname of the image to be shown when the user clicks the imagebutton. Defaults to 'image' if not specified. - - A color, RGB or named, to be used for the button's text when it is disabled. - - The ObjectSystem pathname of the image to be shown when the imagebutton is disabled. Defaults to 'image' if not specified. - - Whether the button is enabled (can be clicked). Default is 'yes'. Also supports dynamic runclient() expressions allowing the enabled status of the button to follow the value of an expression. - - A color, RGB or named, for the text on the button. Default "white". - - A color, RGB or named, for the text's 1-pixel drop-shadow. Default "black". - - Height, in pixels, of the text button. - - The pathname of the image to be shown when the button is "idle". - - The pathname of the image to be shown when the button is pointed-to. Defaults to the 'image' if not specified. - - The distance between the image and text if applicable. - - The text to appear on the button. - - Whether or not the button is tri-state (does not display a raised border until the user points at it). Default is yes. - - There are currently 7 different types. 1) text 2) image 3) topimage (image above text) 4) rightimage 5) leftimage 6) bottomimage 7) textoverimage (text over image background). - - The width, in pixels, of the text button. - - X-coordinate of the upper left corner of the button, relative to its container. - - Y-coordinate of the upper left corner of the button, relative to its container. - - - - - - - - This event occurs when the user clicks the widget. No parameters are available from this event. - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user releases the mouse button on the widget. - - - - - - - - - - - -
- - - - - -

The calendar widget is used to present event/schedule types of data in a year, month, week, or day format. The data for the events on the calendar is obtained from an objectsource's query results, and as such, can be controlled via queries and other forms/tables associated with the same objectsource. The four formats are listed below and can be selected during app run-time.

- -
    - -
  • Year - Presents twelve months at a time in a low-detail type of setting; when events occur on a day, the day is highlighted, but no more data than that is displayed. If the user points the mouse at a given day, it will display in a popup "tooltip" type of manner the events associated with that day. If more than one year's worth of data is in the objectsource's replica, then multiple years are displayed one after the other. Each year is displayed as four rows of three months each, in traditional calendar format.
  • - -
  • Month - Presents one month's worth of data in a medium-detail display. For each month with data in the objectsource, the calendar will display the month as multiple rows, each row containing one week's worth of days. The calendar will attempt to display the various entries for each day, in order of priority, but will limit the amount of data displayed to keep the boxes for the days approximately visually square. Pointing at a day will show the events for the day in a popup, and pointing at an event in the day will show the details for that event.
  • - -
  • Week - Presents one week's worth of data in a high-detail display. For each week with data in the objectsource, the calendar will display the week as seven days with a large vertical extent capable of displaying all events for the day. The left edge of the display will contain times during the day for the day's events to line up with.
  • - -
  • Day - Presents an entire day's worth of data in a high-detail display. For each day with data in the objectsource, the calendar will display the day in full-width, with the day's events listed chronologically from top to bottom.
  • - -
- -

It should be noted that all of the data for the calendar must fit into the objectsource, or else the calendar will not display all of the available data. In this manner the calendar's display must be controlled via the objectsource's data.

- -
- - - -

The calendar widget can be placed inside of any visual container, but because its height can change, it is often placed inside of a scrollpane widget. This widget may not contain other visual widgets.

- -
- - - - An image for the background of the calendar widget. Should be an ObjectSystem path. - - A color, either named or numeric (RGB), for the background of the calendar. If neither bgcolor nor background are specified, the calendar's background is transparent. - - The visual display mode of the calendar - can be 'year', 'month', 'week', or 'day' (see the overview, above, for details on these modes). Defaults to 'year'. - - The name of the date/time field in the datasource containing the event date/time. A mandatory field. - - The name of the string field in the datasource containing the event's full description. If not supplied, descriptions will be omitted. - - The name of the string field in the datasource containing the event's short name. A mandatory field. - - The name of the integer field in the datasource containing the event's priority. If not supplied, priority checking is disabled. - - The height of the calendar, in pixels. If not specified, the calendar may grow vertically an indefinite amount in order to display its given data. If specified, however, the calendar will be limited to that size. - - The minimum priority level for events that will be displayed on the calendar; the values for this integer depend on the nature of the 'priority' field in the data source; only events with priority values numerically higher than or equal to this minimum priority will be displayed. Defaults to 0. - - The color of the words in the calendar. - - The width of the calendar, in pixels. - - X-coordinate of the upper left corner of the calendar, default is 0. - - Y-coordinate of the upper left corner of the calendar, default is 0. - - - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - - - - - - - - - -
- - - -

A chart widget is used to display charts and graphs of data. It uses the Chart.js library.

-
- - -

The chart widget can be placed inside of any visual container, and will attach itself to any objectsource widget that contains it (whether directly or indirectly). Charts may not contain visual widgets.

-
- - - Currently supported types are "bar", "line", "scatter", and "pie". Defaults to "bar". Chart.js supports much more than this, so new types could be added with relative ease. - The position of the legend on the chart. May be "top", "botton", "left", or "right". - The name of the ObjectSource widget which will supply data for the chart. We recommend that you do not specify this directly but instead embed the chart widget within a parent ObjectSource, directly or indirectly. If not specified, the chart will look for an ObjectSource in its parents. - Set to false if you want the y axis to start at the lowest data value. Default is true. - The title of the chart. Default is none. - A color, RGB or named, of the chart title. - The size, in points, of the title font. - - - - - Chart.js has limited support for having multiple chart types on the same axes. The outer chart type must still be specified if this is set. - A color, RGB or named, for the chart element (ex: line) which represents this series. - Set to false to remove the color fill beneath a line chart. Default is true. - A label for the data series. - Which column in the data should be used for the x axis? By default, the chart will pick one for you. - Which column in the data should be used for the y axis? By default, the chart will pick one for you. - - - - Which axis is this? Possible values are "x" and "y". Default is "x". - A label for the axis. Default is none. - - - - - - - - -
- - - - - -

The checkbox widget is used to display a value that can be either true or false (or on/off, yes/no, etc.).It displays as a simple clickable check box.

- -
- - - -

The checkbox widget can be placed inside of any visual container, and will attach itself to any form widget that contains it (whether directly or indirectly). Checkboxes may not contain visual widgets.

- -
- - - - Whether this checkbox should be initially checked or not. Default 'no'. - - This allows the checkbox to be changed. - - Name of objectsource field that should be associated with this checkbox. - - The name of the form that this checkbox is associated with. - - X-coordinate of the upper left corner of the checkbox, default is 0. - - Y-coordinate of the upper left corner of the checkbox, default is 0. - - - - - - - - This event occurs when the user clicks the checkbox. No parameters are available from this event. - - This event occurs when the user has modified the data value of the checkbox (clicked or unclicked it). - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - This takes a given value and sets the check box value to it. - - - - - - - - - - - -
- - - - - -

The childwindow provides the capability of creating a popup dialog or application window within a web page. The window is not actually a separate browser window, but rather a movable container that can appear "floating" above the rest of the Centrallix application.They can take on one of two styles;"dialog" and "window", which currently relate only to how the window border and titlebar are drawn. The windows support windowshading (double-clicking on the titlebar to "rollup" or "rolldown" the window contents).

- -

These "windows" currently do not support multiple instantiation.

- -
- - - -

Childwindows are normally coded at the top-level of an application, since if they are placed within containers, the container limits where the window can be moved (the window is clipped by its container). However, if the "toplevel" property is enabled, then the window floats at the top level of other widgets regardless of the size and location of its container in the application or component (thus the window is not clipped by its container).

- -

These windows can contain other visual and nonvisual widgets.

- -
- - - - A background image for the body of the window. - - A color, RGB or named, to be used as the window body's background.If neither transparent. - - A color that outlines window. - - A radius that describes the sharpness of the corners of the window (smaller means sharper). - - Determines the look of the outline. - - Decides wither the screen closes widthwise (1) or heightwise (2). - - Acts as a boolean to declare if the window is shaded or not (all but title bar minimized). - - A background image for the titlebar of the window. - - A color, RGB or named, for the titlebar of the window. - - Height, in pixels, of the window. - - A pathname to an icon to be used in the upper left corner of the window. If unspecified, the default "CX" ichthus icon is used instead. - - If "yes", the window is modal (and IsModal need not be passed to "Open"). A modal window will force the user to close the window before anything else in the application can be accessed. - - The placement of the shadow described as a rotational transformation with respect to the window. - - The color of the shadow. - - A radius that describes the sharpness of the corners of the shadow (smaller means sharper). - - The placement of the shadow with respect to the window. - - Either "dialog" or "window", and determines the style of the window's border. - - The color for the titlebar's text (window title). Default "black". - - The window's title. - - Whether the window will have a titlebar (and the close "X" in the upper right corner of the window). Default "yes". - - If "yes", the window will float above all other widgets in the application, otherwise it will be clipped by its own container. - - The window is initially visible on screen. The window has an action which can "true". - - Width, in pixels, of the window. - - X-coordinate of the upper left corner of the window, relative to its container. - - Y-coordinate of the upper left corner of the window, relative to its container. - - - - - - Closes the window. Note that widgets inside the window may choose to veto the Close operation: for example, if there is unsaved data in a form. - - Opens the window. If the parameter IsModal is set to 1, then the window becomes modal (only the window's contents are accessible to the user until the window is closed). If the parameter NoClose is set to 1, then the close button in the upper right corner of the window becomes inactive and the window will only close via the Close, SetVisibility, and ToggleVisibility actions. - - Makes the window relocate to a side using a triangle (pop over). - - Opens a window like a pop-up. - - One parameter, "is_visible", which is set to 0 or 1 to hide or show the window, respectively. - - Turns the gshade property(see above) on. - - If the window is visible, this closes it. If it is closed, this opens it. - - Turns the gshade property(see above) off. - - - - - - This event is triggered each time the window is closed (becomes invisible). - - This event is triggered the first time the window is opened. If the window is visible by default, then this event is triggered when the application loads. - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - This event is triggered each time the window is opened (becomes visible). - - - - - - - - - - -
- - - - - -

The clock widget displays the current time on the client computer, and is very configurable in terms of appearance.

- -
- - - -

The clock widget can be used inside of any container capable of having visual subwidgets. It may contain no widgets other than any applicable connectors.

- -
- - - - Whether to show "AM" and "PM", default is 'yes' if 'hrtype' is 12, and cannot be set to 'yes' if 'hrtype' is 24. - - An image for the background of the clock widget. Should be an ObjectSystem path. - - A color, either named or numeric, for the background of the clock. If neither bgcolor nor background are specified, the clock is transparent. - - Acts as a boolean to declare if the type will be bold (larger and thicker). - - A color, either named or numeric, for the foreground text of the clock. - - A color, either named or numeric, for the foreground text's shadow, if 'shadowed' is enabled. - - Stores the field name and memory. - - The height of the clock, in pixels. - - Set to 12 for a 12-hour clock, or to 24 for military (24-hour) time. - - Acts as a boolean to control if the entire clock can be moved. - - Whether or not the seconds should be displayed. Default is 'yes'. - - Whether or not the clock's text has a shadow. Default is 'no'. - - The horizontal offset, in pixels, of the shadow. - - The vertical offset, in pixels, of the shadow. - - The size, in points, clock's text. - - The width of the clock, in pixels. - - The X location of the left edge of the widget. Default is 0. - - The Y location of the top edge of the widget. Default is 0. - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - -
- - - - - -

This widget is used to instantiate a custom component that has already been defined using a widget/component-decl widget, typically inside a ".cmp" file. The instantiation can be either static or dynamic: a static component is rendered along with the component or application that it resides inside, whereas a dynamic component is loaded as needed from the client. Components may also allow multiple instantiation when dynamic, which is especially beneficial with components whose top-level widget happens to be a widget/childwindow.

- -
- - - -

A component can be either visual or non-visual, and can be placed at almost any point in the application.

- -

At the time of writing, only connectors may be placed inside a widget/component. It is planned that it be possible to place widgets inside a widget/component which then appear inside specially designated containers within the component itself. However that capability is not available at present.

- -
- - - - If enabled (dynamic single-instantiation components only), when a component is instantiated a second time, the original component is automatically destroyed so there is at most one instance at a time in existence. Defaults to 'yes'. - - Can specify another form to be a child of (if different than the implied form in which this control is nested). - - Height, in pixels, of the component. If unset, this defaults to the height of the component's containing widget. - - Either "static" or "dynamic". A static component is rendered with the application, whereas a dynamic one is loaded as needed from the client. Defaults to 'static'. - - If enabled (dynamic components only), the component can be instantiated more than once on the client. Defaults to 'no'. - - The path, in the OSML, to the component's definition (.cmp) file. (e.g. /sys/cmp/smart_field.cmp, /sys/cmp/form_controls.cmp, /samples/button.cmp). - - Acts as a boolean to indicate if there are any components on a higher level. - - A string that represents the data type. - - Width, in pixels, of the component. If unset, this defaults to the width of the component's containing widget. - - X-coordinate of the upper left corner of the component. If unset, defaults to 0. - - Y-coordinate of the upper left corner of the component. If unset, defaults to 0. - - - - - - Destroys the component. If multiple instances exist, then all instances are destroyed. - - Instantiates the component. - - - - - - This event is triggered when a dynamic component completes loading from the server. - - - - - - - - Type of smart field (e.g. label, editbox, checkbox, datetime). - - (e.g. readonly). Must have field be set in order to be "readonly". - - This is something which can accessed as :this_widget:value (returns the value of text which can't be directly accessed?), but can not be set directly through this property -- see SetValue listed under the Action section. - - - - - - (e.g. 1) - - Title to put across the form control bar (Note: the form_controls.cmp allows the user to perform row operations on a given record set). - - - - - - This is something which can accessed as :this_widget:content (returns the value of text which can't be directly accessed?), but can not be set directly through this property -- see SetValue listed under the Action section. - - - - - - - - - - - -
- - - - - -

The component-decl widget is used to define a new component which can be used and reused in other applications or components. Creating a component is very similar to creating an application - except that the top-level widget in a component is "widget/component-decl" instead of "widget/page".

- -
- - - -

A widget/component-decl widget must occur at the top level of a component (.cmp) file.

- -

Other visual and nonvisual widgets may be placed inside a component-decl, in addition to parameters and declarations of Events and Actions that the component generates and handles.

- -

To declare that a component generates an Event, place a "widget/component-decl-event" inside the component at the top level. No parameters are needed for that Event. To cause the component to generate the Event, trigger an Action with the same name on the component-decl from inside, and the event will be activated for the containing application or component.

- -

Similarly, to declare that a component can receive an Action, place a "widget/component-decl-action" inside the component at the top level. Again, no parameters are needed. The containing application or component can then trigger the Action, which will cause an event to occur inside the component. The event occurs on the component-decl widget (top level of the component), and can be caught with a connector widget.

- -

Components can take parameters just like applications can. See the "widget/parameter" widget for details on how to declare parameters on applications and components.

- -

Several of the properties of a component-decl are used to make it easy to wrap another widget or component. The expose_actions_for, expose_events_for, and expose_properties_for properties cause the actions, events, and properties of a widget or component inside the component-decl to be exposed to the "outside world". The apply_hints_to property causes any presentation hints ("widget/hints") that are applied in the instantiation of a component-decl (inside the "widget/component") to be applied to the specified widget or component inside the component-decl.

- -
- - - - Specifies a widget inside the component-decl that will receive any presentation hints constraints ("widget/hints") that are applied to the instantiation of this component-decl. - - Specifies a widget inside the component-decl whose actions (methods) we want to expose on the external interface of this component-decl. - - Specifies a widget inside the component-decl whose events we want to expose on the external interface of this component-decl. - - Specifies a widget inside the component-decl whose client-side properties we want to expose on the external interface of this component-decl. - - Indicates if the component is seen (not = -1). - - - - - - - Sends an alert to the screen. - - Loads the widget. - - Allows events to happen. - - - - - - Occurs when the widget has finished launching. - - - - - - - - - -
- - - - - -

Each widget can have events and actions associated with it. The events occur when certain things occur via the user interface, via timers, or even as a result of data being loaded from the server. Actions cause certain things to happen to or within a certain widget, for example causing an HTML layer to reload with a new page, or causing a scrollable area to scroll up or down.

- -

The connector widget allows an event to be linked with an action without actually writing any JavaScript code to do so -- the connector object is created, and given an event to trigger it and an action to perform when it is triggered.

- -

Events and actions can have parameters, which specify more information about the occurrence of an event, or which specify more information about how to perform the action. Such parameters from events can be linked into parameters for actions via the connector widget as well.

- -

When supplying a parameter to a connector (other than "action", "event", and "target"), there are several ways that parameter can be specified. First the parameter can be a constant value, defined explicitly in the application. Second, the parameter can be an expression which is evaluated on the client (using the runclient() pseudo-function) and which can change based on the conditions under which the connector is triggered. In this latter case, the runclient() expression can contain parameters supplied by the event, or values supplied by other widgets.

- -
- - - -

The connector object should be a sub-object of the widget which will trigger the event. Any widget with events can contain connectors as subwidgets.More than one connector may be attached to another widget's event.

- -

An example connector would link the click of a URL in a treeview with the loading of a new page in an HTML area. See the Example source code at the end of this document to see how this is done.

- -
- - - - The name of the action which will be activated on another widget on the page. - - The name of the event that this connector will act on. - - The name of the widget generating the event (defaults to the widget the connector is placed inside of). - - The name of another widget on the page whose action will be called by this connector (if unspecified, defaults to the widget the connector is placed inside of). - - - - - - - - - - - -
- - - - - -

The datetime widget displays a calendar and clock. Input is done by typing into the bar or by selecting a date in display pane. When the form goes into query mode two panes will appear, one for the start and one for the end time. Note that the osml format for searching for dates is dd Mon yyyy, whereas the format dates are displayed in the widget is Mon dd, yyyy.

- -
- - - -

This widget can be placed within any visual widget.

- -
- - - - The background color of the bar and pane (named or numeric). - - Default "no". If set to "yes" indicates that the datetime widget should only allow select and display a date, not a date and time. It may be useful to set default_time when using this option. - - When the user sets the date without selecting a time value (or if date_only is set), use this time as the "default" time. This can be set to "00:00:00" or "23:59:59", for example, to specify a time at the start or end of a day. - - The foreground color of the bar and pane (named or numeric). - - Not used currently to my knowledge. - - Name of a form object that is a parent of the datetime widget (not required). - - Height, in pixels, of the bar. - - Used to set the initialdate by a string. - - Default "yes". Specifies that when the datetime widget is in a form that is in search (QBF) mode, the datetime should display both a "start" and an "end" calendar so the user can search for records/objects matching a range of dates. - - Used to set the initial date by a query. - - Width, in pixels, of the bar. - - X-coordinate of the upper left corner of the bar. - - Y-coordinate of the upper left corner of the bar. - - - - - - This event occurs when the user clicks the widget. No parameters are available from this event. - - This event occurs when the user has modified the data value of the widget (clicked or unclicked it). - - This event occurs when the datetime widget receives keyboard focus (if the user tabs on to it or clicks on it, for instance). - - This event occurs when the datetime widget loses keyboard focus (if the user tabs off of it, for instance). - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - - - - Generates an internal data object using the data specified or the current time. - - - - - - - - - -
- - - - - -

A dropdown form element widget that allows one of several options to be selected in a visual manner. The options are filled in using one of the child widgets, or via an SQL query to a database defined below.

- -
- - - -

This widget can be placed within any visual widget (or within a form widget). It may only contain 'dropdownitem' child objects, although it may of course also contain connectors as needed.

- -
- - - - The background color for the widget. - - Fieldname (from the dataset) to bind this element to. - - Represents the widget form (groups many widgets into one widget). - - Height, in pixels, of the dropdown. - - The color of the highlighted option when the mouse goes over the item. - - The default widget that has focus when the dropdown is first activated. - - If this is set to objectsource then the dropdown acts an objectsource client. - - Number of widgets displayed at once in the dropdown box. - - Represents the widget object source (transfers data to and from server). - - Width of popup dropdown list. - - If set to yes, this indicates that when the dropdown's form is in search (QBF) mode, the dropdown will allow multiple items to be selected, so the user can search for records/objects that match one of several values. The values will be listed in the dropdown, separated by commas. - - The SQL used to retrieve the list of items for the dropdown. It should have between two and five columns, in this order: label, value, selected (0 or 1, whether the item is selected by default), grp (group name), hidden (0 or 1 to hide the item from the dropdown list but still allow it to be a valid value). - - Width, in pixels, of the dropdown. - - X-coordinate of the upper left corner of the dropdown, default is 0. - - Y-coordinate of the upper left corner of the dropdown, default is 0. - - - - - - - - The text label to appear in the dropdown listing. - - The actual data that is stored with this item. - - - - - - - - This event occurs when the user has modified the data value of the widget (clicked or unclicked it). - - This event occurs when the data is changed (occurs when key press or button changes things). - - This event occurs when the edit bar receives keyboard focus (if the user tabs on to it or clicks on it, for instance). - - This event occurs when the edit bar of the dropdown loses keyboard focus (if the user tabs off of it, for instance). - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - This event occurs when right click is used on the mouse. - - - - - - Deletes all the widgets in the dropdown menu. - - This causes the dropdown to display a different Group of items (use the Group parameter). It can also restrict what items are displayed based on a minimum or maximum value (use Min and Max parameters). - - This specifies a SQL (use "SQL" parameter) query to use to re-load the contents of the dropdown. It should have between two and five columns, in this order: label, value, selected (0 or 1, whether the item is selected by default), grp (group name), hidden (0 or 1 to hide the item from the dropdown list but still allow it to be a valid value). - - Changes the child property "value". - - - - - - - - - -
- - - - - -

An editbox form element widget allows the display and data entry of a single line of text (or a numeric value). When the editbox is clicked (and thus receives keyboard focus), the user can type and erase data inside the widget. Data which does not fit will cause the text to scroll. When it receives keyboard focus, the editbox displays a flashing I-beam cursor. The cursor color uses the data focus color for the page (default is '#000080', or dark blue).

- -

When an editbox is part of a form and the form goes into query mode the editbox has several additional features. First, you can use the * operator as a wildcard (e.g. *ber matches September, October, November, December). Second you can enter a range if the field is numeric (e.g. 1-100). Thirdly, a list of criteria can be specified, separated by commas.

- -
- - - -

The editbox can be placed within any visual widget (or within a form widget). Editboxes automatically attach themselves to the form which contains them (whether directly or indirectly). Editboxes cannot contain visual widgets.

- -
- - - - A background image for the contents of the editbox. - - A color, RGB or named, for the editbox contents. If neither bgcolor nor background is specified, the editbox is transparent. - - A color, RGB or named, editbox's value description. The value description is displayed to the right of the value in the editbox, in parentheses, and in a different color. - - Specifies wither the edit box can be written in, default is true meaning the edit box cannot be written in. - - The value description to display in the editbox when the editbox is empty. For instance, "optional" or "required" or "type search and press ENTER" might be commonly used empty_description settings. - - Name of the column in the datasource you want to reference. - - Height, in pixels, of the editbox. - - Number of characters to accept from the user. - - Set to 'yes' if the user cannot modify the value of the edit box (default 'no'). - - Either "raised" or "lowered", and determines the style of the border drawn around the editbox. Default is "lowered". - - A string to display in a tooltip when the user rests the mouse pointer over the editbox. By default, the editbox will display a tooltip of its content if the content is too long to fit in the visible part of the editbox. - - Width, in pixels, of the editbox. - - X-coordinate of the upper left corner of the editbox, default is 0. - - Y-coordinate of the upper left corner of the editbox, default is 0. - - - - - - - - This event occurs before the data changes and can stop the data change event. - - This event occurs before the key press event is fired and can stop the key press event. - - This event occurs when the user clicks the widget. No parameters are available from this event. - - This event occurs when the user has modified the data value of the widget (clicked or unclicked it). - - This event occurs when the data is changed (occurs when key press or button changes things). - - This event occurs when the user presses the escape key. - - This event occurs when the editbox receives keyboard focus (if the user tabs on to it or clicks on it, for instance). - - This event occurs when the user presses any key. - - This event occurs when the editbox loses keyboard focus (if the user tabs off of it, for instance). - - This event occurs when the user releases the mouse button on the widget. - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user presses the return key. - - This event occurs when the user presses the tab key. - - - - - - The Enable action allows an edit box to be written in, and takes no parameters x from being written in, and takes no parameters. - - The SetFocus action selects and gives control to an edit box. - - The SetValue action modifies the contents of an editbox, and takes a single parameter, 'Value' (string). - - This action modifies the "value description" of the editbox, and takes a single parameter, 'Description' (string). - - - - - - - - - -
- - - - - -

The execmethod widget is a nonvisual widget which is used to call ObjectSystem methods on objects on the server.This widget may become deprecated in the future when more advanced OSML API widgets become available.

- -

These widgets are used via the activation of their "ExecuteMethod" action.

- -
- - - -

The execmethod widget, since it is nonvisual, can be placed almost anywhere but is typically placed at the top-level (within an object of type "widget/page") for clarity's sake. It has no effect on its container.These widgets cannot contain visual widgets, and since they have no Events, normally contain no connector widgets either.

- -
- - - - The name of the method to invoke. - - The ObjectSystem pathname of the object on which the method is to be run. - - A string parameter to pass to the method being invoked. - - - - - - Action causes the widget to execute the method on the server. It can take three parameters, which default to those provided in this widget's properties: "Objname", the object path, "Method", the method to invoke, and "Parameter", the parameter to pass to the method being invoked. - - - - - - - - - -
- - - - - -

The form widget is used as a high-level container for form elements. Essentially, a form widget represents a single record of data, or the attributes of a single object in the objectsystem (or of a single query result set object). Form widgets must be used in conjunction with an ObjectSource widget, which does the actual transferring of data to and from the server.

- -

Forms have five different "modes"of operation, each of which can be specifically allowed or disallowed for the form.

- -
    - -
  1. No Data - form inactive/disabled, no data viewed/edited.
  2. - -
  3. View - data being viewed readonly.
  4. - -
  5. Modify - existing data being modified.
  6. - -
  7. Query - query criteria being entered (used for query-by-form applications)
  8. - -
  9. New - new object being entered/created.
  10. - -
- -

Occasionally, the user may perform an operation which inherently disregards that the form may contain unsaved data. When this occurs and there is newly created or modified data in the form, the application must ask the user whether the data in the form should be saved or discarded, or whether to simply not even perform the operation in question. Since DHTML does not inherently have a "three-way confirm" message box (with save, discard, and cancel buttons), Centrallix allows a form to specify a "three-way confirm" window. This should be a hidden (visible=no) "widget/htmlwindow" object which may contain any content, but should at least contain three buttons named "_3bConfirmSave", "_3bConfirmDiscard", and "_3bConfirmCancel" directly in the htmlwindow. During a confirm operation, this window will become "application-modal"; that is, no other widgets in the application may be accessed by the user until one of the three buttons is pushed.

- -

Several settings on the form widget control what state, or "mode", the form can be in: allow_query, allow_new, allow_modify, allow_view, and allow_nodata. These can beused to constrain a form to perform a specific task, such as only searching, or only creating new records. For example, a form with only allow_search enabled will always return to the search (QBF) mode and will never display the searched-for data that is returned in the objectsource.

- -
- - - -

Although the form widget itself is nonvisual in nature, it can contain visual widgets, including other containers, which may then in turn contain form elements as well. The form widget may be contained within any widget with a visual container (otherwise, the form elements would not show up on the page).

- -
- - - - Allow deletion of the displayed record. - - Allow modification of existing records. - - Allow creation of new records. - - Allow the 'no data' state. - - Default "no". If this is set to "yes", then the form will permit its data to be obscured (hidden from the user, via a window closure or tab page switch) and unsaved at the same time. If set to "no", the form will require the user to save or cancel any data modifications before the form may be obscured. - - Allow query by form. - - Allow viewing of existing data. - - Default "yes". Whether to automatically place the focus on the first form element in the form when the form transitions to the New, Modify, or Search mode for any reason other than the user manually putting focus on one of the form elements. - - Whether to pop up an OK/Cancel message box asking the user whether he/she is sure the record should be deleted. - - Set to true when the discard button of the 3 buttom confirm window (3bconfirmwindow) is pressed. - - Can be "save" (default), "nextfield", or "lastsave". Controls what to do when the user presses ENTER while in a form element in this form. "save" means to save the record immediately. "nextfield" means to move to the next form field, as if TAB were pressed instead. "lastsave" means to move to the next form field, but once ENTER is pressed on the very last field, to then save the record. Most people prefer "save" for this, since that is consistent with how normal applications work, but people in finance or accounting work often prefer "lastsave" since it allows them to perform high-speed data entry using a numeric keypad. - - Stores wither this form can be grouped with other forms. - - Enable MultiEnter. - - Specifies another form to transfer focus to when the user presses TAB at the end of the form. - - Similar to "next_form", but searches for the next form after this one within a given widget or component. Transitioning from one component context to another is permitted (you could specify a "widget/page" widget here, and cause focus to transfer to another form in the same application, regardless of level of component nesting; this may or may not be desired behavior). - - Represents the widget object source (transfers data to and from server). - - Allow changes. - - How to react to a tab in a control. - - When tabbing between form elements, if this is set to "yes", do not transfer focus to form elements that are not currently visible to the user. - - The name of the window to use for all 3-way confirm operations (save/discard/cancel). - - - - - - - - Fieldname (from the dataset) to bind this element to. - - - - - - - - - - - - This event occurs just before the form is enabled after a save. - - This event occurs when a child controller changes its data. - - This event occurs when the delete function is successfully completed. - - This event occurs when it is confirmed that the method search for object is available. - - This event occurs when the save action was successful. - - This event occurs after the discard action is performed. - - This event occurs at the end of the change mode action (always with status change event). - - This event occurs if and only if there was a mode change and the new mode is the 'Modify' state (always occurs with both a statusChange event and a ModeChange Event). - - This event occurs if and only if there was a mode change and the new mode is the 'New' state (always occurs with both a statusChange event and a ModeChange Event). - - This event occurs if and only if there was a mode change and the new mode is the 'NoData' state (always occurs with both a statusChange event and a ModeChange Event. - - This event occurs if and only if there was a mode change and the new mode is the 'Query' state (always occurs with both a statusChange event and a ModeChange Event. - - The even occurs when the form changes modes or when a child control changes its data. - - This event occurs if and only if there was a mode change and the new mode is the 'View' state (always occurs with both a statusChange event and a ModeChange Event. - - - - - - Clears the form to a 'no data'state. - - Deletes the current object displayed. - - Cancels an edit of form contents. - - Prevents interaction with the entire form. - - Allows editing of form's contents. - - Allows interaction with the form. - - Moves the form to the first object in the objectsource. - - Moves the form to the last object in the objectsource. - - Allows creation of new form contents. - - Moves the form to the next object in the objectsource. - - Moves the form to the previous object in the objectsource. - - Allows entering of query criteria. - - the query and returns data. - - Either executes a query or allows one to be made depending on the previous state. - - Saves the form's contents. - - Modifies a field and puts the form into a new modify or search mode if appropriate. - - Attemps to save data, if there is an error it returns false. - - Debugging tool to test the 3bcomfirm method (only use if you know what this method is). - - Changes the form to view mode. - - - - - - - Whether the form has a status or modifications that can be discarded / canceled. - - True if the form is in a state where the data can be edited (but isn't currently being edited). - - True if the form is in a state where a new record can be created. - - Whether the form can be placed into "QBF" (query by form) mode so that the user can enter query criteria. - - Whether the form is in "QBF" (query by form) mode so that the query can actually be run. - - Whether the form has data in it that can be saved with the Save action. - - The number of the last record in the query results, starting with '1'. Null if no data is available or if the last record has not yet been determined. - - The current record in the data source being viewed, starting with '1'. Null if no data is available. - - - - - - - - - -
- - - - - -

Many times with multi-mode forms like those offered by Centrallix, the end-user can become confused as to what the form is currently "doing" (for instance, is a blank form with a blinking cursor in a "new" state or "enter query" state?). Centrallix helps to address this issue using the form status widget. A form status widget displays the current mode of operation that a form is in, as well as whether the form is busy processing a query, save, or delete operation. This clear presentation of the form's mode is intended to clear up any confusion created by a multi-mode form. This widget is a special-case form element.

- -
- - - -

The form status widget can only be used either directly or indirectly within a form widget. It can contain no visual widgets.

- -
- - - - The name of the form that this instance of form status corresponds to. - - Sets the style of the form widget. Can be "small", "large", or "largeflat". - - Allows you to set x position in pixels relative to the window. - - Allows you to set y position in pixels relative to the window. - - - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - - - - - - - - - -
- - - - - -

The frameset widget provides the ability to construct a page consisting of multiple (potentially resizeable) frames.It is one of two possible top-level widgets (the page widget is the other one). Framesets can consist of one or more frames, arranged either in rows or columns.

- -
- - - -

The frameset can either be a top-level widget, or can be contained within a frameset (for subframes).The frameset widget should not be used anywhere else in an application. The frameset should contain only other framesets and/or pages.

- -
- - - - Number of pixels wide the border(s) between the frame(s) are. Can be set to zero. - - Whether the frames are arranged in rows or columns. Set this attribute to "rows" or "columns"respectively. - - Title of the frameset for an html page. - - - - - - - - Large this frame will be.Can either be expressed as an integer, which represents "50%". - - Width, in pixels, of the margins of the frame. - - - - - - - - - - - -
- - - - - -

An autolayout widget with style set to "hbox". See "widget/autolayout".

- -
- -
- - - - - -

The hints widget stores default values and other component modifying properties.

- -
- - - -

The hints widget can be placed inside of any visual component. Hints do not contain visual widgets.

- -
- - - - Defines a set of acceptable characters (e.g. allowchars="0123456789"). - - Defines a set of unacceptable characters. - - Defines a condition that the parent value must meet to be valid. - - A string or integer that specifies the initial, default, starting value for the component which containing this widget/hints widget. - - A list of string values that give all the possible values of th the parent. - - A sql query that is used to get the required value list. - - The way in which data is presented to the user. - - A numeric identification for a group of attributes. - - A string identification for a group of attributes. - - Determines rows of the widget. - - Length in characters that can be entered into this field. - - A string or integer that stores the maximum value of the attributes of its parent widget. - - A string or integer that stores the minimum value of the attributes of its parent widget. - - If the field is a bitmask, determines which ones are read only. - - Optional: contains a combination of 1 or more items following set {readonly, alwaysdef} separated by a comma if multiple items are chosen. - - Number of characters in the field shown to the user at once. - - - - - - - - - - - - -
- - - - - -

The HTML area widget provides a way to insert a plain HTML document into a Centrallix generated page, either in-flow (static) or in its own separate layer that can be reloaded at will (dynamic). The HTML document can either be given in a property of the widget or can be referenced so that the HTML is read from an external document.

- -

The HTML area widget also can act as a mini-browser -- clicking on hyper-text links in the visible document will by default cause the link to be followed, and the new document to be displayed in the HTML area (if the HTML area is dynamic).

- -

Dynamic HTML areas do double-buffering and allow for transitions (fades) of various types.

- -
- - - -

The HTML area widget can be placed within any other widget that has a visible container (such as panes, pages, tab pages, etc.).

- -
- - - - Static contents for the HTML area. Usually used in lieu of "source" (see below). - - HTML source to be inserted at the end of the HTML document. - - height, in pixels, of the HTML area as a whole. - - Either "static" or "dynamic", and determines whether the HTML area is in-flow ("static") can be positioned and reloaded at-will. - - HTML source to be inserted at the beginning of the HTML document. - - The objectsystem path or URL containing the document to be loaded into the HTML as local server. - - Width, in pixels, of the HTML area as a whole. - - Dynamic HTML areas, the x coordinate on the container of its upper left corner. - - Dynamic HTML areas, the y coordinate on the container of its upper left corner. - - - - - - - - Loadpage action takes two parameters. "Source" contains the URL for the new page to be loaded into the HTML area.The optional parameter "Transition" indicates the type of fade to be used between one page and the next.Currently supported values are "pixelate", "rlwipe", and "lrwipe". - - - - - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - - - - -
- - - - - -

The image widget displays a picture (image).

- -

This widget can be a form element, in which case it will display an image as specified by the data from the form.

- -
- - - -

The image widget can be placed inside any container that allows for visual widgets. Only connectors may be placed inside it.

- -
- - - - Determines wither the image can be stretched or must stay its original aspect ration. - - If using a form for the image path, the name of the column in the datasource you want to reference. - - If using a form for the image path, this is the name of the form. If left unspecified, and fieldname is supplied, the default will be whatever form the image is inside. - - Height, in pixels, of the image. - - The words that come up if the image object cannot find its source and display an image. - - Width, in pixels, of the image. - - X-coordinate of the upper left corner of the image, relative to its container. - - Y-coordinate of the upper left corner of the image, relative to its container. - - - - - - This event occurs when the user clicks the checkbox. No parameters are available from this event. - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - Determines how much the image grows or shrinks compared to the original. - - An OSML pathname for the location of the image (such as a png or jpg file). - - Distance it is along the x axis from its parent widget. - - Distance it is along the y axis from its parent widget. - - - - - - Displays the image to user. - - Reloads the image with new offset values. - - Reloads the image with a new scale value. - - - -
- - - - - -

The ImageButton widget provides a clickable button that is comprised of a set of two or three images.The first image is shown normally when the button is idle, the second when the button is pointed-to, and the third image is shown when the button is actually clicked. This provides a "tri-state" appearance much like that provided by buttons in modern user interfaces, although the button can be two-state, with just an "unclicked" and "clicked" version of the image.

- -

The images are automatically swapped out when the appropriate mouse events occur on the button.

- -

ImageButtons can also be disabled, and a "disabled" image is displayed at that time.

- -

Unlike the textbutton, there is no 'tristate' property for an imagebutton; to make an imagebutton tri-state (different image when idle vs. when pointed to), use a 'pointimage', otherwise do not specify 'pointimage'.

- -
- - - -

The ImageButton can be placed inside any visible container, but only nonvisual widgets can be placed within it.

- -
- - - - The ObjectSystem pathname of the image to be shown when the user clicks the imagebutton. Defaults to 'image' if not specified. - - The ObjectSystem pathname of the image to be shown when the imagebutton is disabled. Defaults to 'image' if not specified. - - Height, in pixels, of the image button. - - The pathname of the image to be shown when the button is "idle". - - The pathname of the image to be shown when the button is pointed-to. Defaults to the 'image' if not specified. - - Whether to repeat the click event multiple times while the user holds down the button. - - The text that appears when the courser hovers over the image button. - - Width, in pixels, of the image button. - - X-coordinate of the upper left corner of the button, relative to its container. - - Y-coordinate of the upper left corner of the button, relative to its container. - - - - - - - - This event occurs when the user clicks the button. No parameters are available from this event. - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - This action causes the image button to enter its 'disabled' state, displaying the 'disabledimage' if specified. - - This action causes the image button to enter its 'enabled' state, possibly changing its appearance if a 'disabledimage' was explicitly specified. - - - - - - Whether or not the imagebutton should be enabled. Defaults to 'yes'. Can be an expression that if uses runclient() will cause the enabled status of the imagebutton to 'follow' the value of that expression while the application is running. - - - - - - - - - -
- - - - - -

The label widget is used to display a non-editable string of text. It displays as a simple label.

- -

A label can be used as a form element; to do this, specify a fieldname, and optionally a form.

- -

A label can behave like a "link": to do this, specify a point_fgcolor and a click_fgcolor.

- -
- - - -

The label widget can be placed inside of any visual container, and will attach itself to any form widget that contains it (whether directly or indirectly). Labels may not contain visual widgets.

- -
- - - - Describes how the text should align in the label (e.g. right). - - It determines if the text can wrap around something. - - The color (named or #numeric) of the text in the label when the user clicks on the label. - - The color (named or #numeric) of the text in the label. - - The name of the column in the datasource that will be used to supply the text of the label. - - (e.g. 16, default: 12). - - The name of the form that this label is associated with. - - The height of the label. - - If text is no longer able to display shows an ellipsis. - - The color (named or #numeric) of the text in the label when the user hovers the mouse over the label. - - (e.g. bold). - - The text that the label is to display. - - The text displayed when the courser hovers over the widget. - - Describes how the text should align vertically within the label (e.g. top, middle, or bottom). - - The width of the label. - - X-coordinate of the upper left corner of the checkbox, default is 0. - - Y-coordinate of the upper left corner of the checkbox, default is 0. - - - - - - - - This event occurs when the user clicks the checkbox. No parameters are available from this event. - - This event occurs when the user has modified the data value of the checkbox (clicked or unclicked it). - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - Sets the value property to the given parameter. - - - - - - This property allows a runclient() expression to dynamically supply the text to display in the label. - - - - - - - - - -
- - - - - -

The menu widget is used to create popup menus, drop-down menus, and menu bars. Menu widgets consist of a series of menuitem widgets which compose a menu.

- -

Menu items are generally linked to actions on a page via the use of connectors. Simply place the connector inside the menu item widget.

- -

Note: as of the time of writing, menu widgets were not yet properly functional.

- -
- - - -

Menus can be placed inside of any visual container. However, be aware that the menu will be clipped by its container, so placing them at the top-level can be of an advantage. Menu widgets contain menuitem widgets, which are also described in this section.

- -
- - - - A background image for a menu item that is selected (clicked on). - - A color, RGB or named, to be used as a selected (clicked) item's background. If neither active_bgcolor nor active_background are specified, the 'highlight' color or background is used instead. - - A background image for the menu. - - A color, RGB or named, to be used as the menu's background. If neither bgcolor nor background are specified, the menu is transparent. - - The width of the data elements in the menu. - - Either "horizontal" or "vertical" (default), and determines whether the menu is a drop-down/popup (vertical) or a menubar (horizontal). - - A color for the menu's text. - - Height, in pixels, of the menu, for menus with a direction of 'horizontal'. - - A background image for a menu item that is highlighted (pointed at). - - A color, RGB or named, to be used as a highlighted (pointed-at) item's background. If neither highlight_bgcolor nor highlight_background are specified, the standard color or background is used instead. - - Default "no". Popup menus disappear after an item on them is selected, whereas fixed menus remain visible (such as for menubars). - - Height, in pixels, of the menu items in a menu. - - Determines how far the shadow translates from the menu. - - Describes in a ratio how much larger or smaller the size of the shadow is compared to the window. - - Width, in pixels, of the menu. For menus with a direction of 'vertical', an unspecified width is determined dynamically based on the menu contents. - - X-coordinate of the upper left corner of the menu, relative to its container. - - Y-coordinate of the upper left corner of the menu, relative to its container. - - - - - - - - Optionally, a checkbox can be displayed to the left of the menu item when 'checked' is specified. In this case, the 'value' can also be a runclient() expression that controls whether the menu item is checked. - - Optionally, the pathname of an image file to display to the left of the menu item. - - The text to appear on the menu item. - - If set to "yes", then the menu item will be displayed on the righthand side of a horizontal menu bar (e.g., for having "File" "Edit" "Tools" on the left, and "Help" on the far right). - - The 'value' of the menu item, passed to the Selected event, below. If not specified, it defaults to the name of the widget (not its label). - - - - - - - - - - The text to appear on the menu title. - - - - - - - - This event occurs when the an item in the menu which is not a submenu is changed. - - This event occurs when the menu which previously was not clicked is clicked. - - This event occurs when the menu which previously was clicked is unselected. - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - This event fires when a menu item is selected. It can be placed in the menu itself, or inside the menu item widget. When on a menu item, it only fires when that item is selected. When on a menu, it passes the selected item's value as a parameter named 'Item' (string). - - - - - - This action causes a popup-type menu to become visible and appear at a selected (x,y) position on the page. When the user selects an item on the menu or clicks elsewhere on the page, the menu then disappears. Takes two parameters - X and Y, the (integer) positions on the page for the menu to appear. - - - - - - - - - -
- - - - - -

The objectsource (osrc) widget lies at the core of Centrallix's ability to dynamically exchange data between the server and client. This widget implements a form of "replication" by maintaining a replica of a small segment of data in the user agent.

- -

Both form and dynamic table widgets interact with the objectsource nonvisual widget to acquire data, update data, create data, and delete data. In fact, it is possible for more than one form and/or table to be connected with a given objectsource, to perform a variety of functions.

- -

Objectsources offer synchronization with other objectsources via the Sync and DoubleSync actions (see below) or rule-based connnectivity (see widget/rule). These actions allow the application to contain multiple objectsources with primary key / foreign key relationships, and to have those objectsources automatically stay in synchronization with each other based on those relationships.

- -

An objectsource may also be used to run a query which does not return any rows, such as an insert, update, or delete query. Under normal data maintenance conditions such queries are not needed as the objectsource handles those operations internally, however.

- -
- - - -

Objectsource widgets are often used at the top-level of an application, within a "widget/page" object, but can be used anywhere. The forms/tables that use an objectsource are generally placed within that objectsource. A page may have more than one objectsource widget.

- -

Even though the objectsource is nonvisual, it is, like the form widget, a container, and can (should!) contain other visual and nonvisual widgets.

- -
- - - - One of oneachreveal, never, onload, onfirstreveal. (note: this autoquery setting is different from, but related to, the "widget/rule" "autoquery"). "onload" means that the osrc should run its query automatically when the .app containing this osrc is loaded by the user. "onfirstreveal" means that the osrc should run its query automatically when the data (e.g., a table or form) is first displayed to the user (e.g., the containing childwindow becomes visible or tabpage is selected). "oneachreveal" means to do so *each* time the data is displayed to the user. "never" means that the osrc should never query automatically by itself, but it may be triggered by a connector (QueryParam, QueryText, etc.) or by a widget/rule of type osrc_relationship. Important Note: If you expect to normally trigger the osrc via a relationship or via QueryParam, it is often *best* to set autoquery to 'never'. Otherwise, unexpected results can sometimes occur. - - If inserts and deletes are to function, the ObjectSystem pathname in which those inserts and deletes should occur. This should be one of the objects specified in the FROM clause of the SQL statement. - - Set of search parameters. - - Whether normal query activity on this objectsource indicates to the server that the user is active. Some objectsources (in conjunction with a widget/timer) regularly refresh their data; for those, indicates_activity can be set to "no" so that those refreshes don't cause a user's session to never time-out. - - The name of the object in the SQL query that contains the primary key for the query. - - Represents the number to fetch from the server when more records are needed from the server (such as when a form requests that the Current Record be a record beyond the end of the current replica contents). - - ** This feature currently disabled in Centrallix 0.9.1 ** Default "no". If set to "yes", the objectsource will ask the server to send it updates on any changes that occur on the server side (i.e., if the changes were made by another objectsource or by another user, they would be automatically refreshed into this objectsource in near real-time). - - The time between the data refreshing, if set to 0 it does not automatically refresh. - - Represents the number of records to store in its replica. This value should be larger than the maximum number of records that will be displayed at any one time. At times, Centrallix may increase the number of records cached on the client beyond this number. - - Acts as a boolean and delays query until the osrc is visable (if "true"). - - Similar to readahead, but relates to how many records should be fetched when more records are needed from the server to fulfill a request to display more data, such as a table widget scrolling without changing the current record. By default this is set to the value of 'readahead'. - - Default "yes". If set to "no", the objectsource will not relay any updates (modifications, etc.) that the user makes to the server. - - The SQL statement used to retrieve the data. - - Default "no". If set to "yes", then queries will be performed using a HAVING clause instead of a WHERE clause. - - - - - - - - When autoquery is set to true, when the master changes, the slave automatially requeries (otherwise have to explicitly call requery or refresh on the slave osrc). When autoquery is false, it causes relationships to be in enforced, but doesn't cause a re-query when the master's osrc refreshes / requeries. - - Defaults to 'yes'. If it is set to 'no', then this osrc is set up to be a master of the target, otherwise the default is for it to be a slave of the target. - - The field names in this objectsource to be used as the key value for the relationship. Keys can be key_1 through key_5. - - One of: allrecs, norecs, or sameasnull (the default). If the master osrc has no data loaded in it (no records), this determines how the slave (child) osrc behaves. 'allrecs' means to query for all records that match the sql query for the osrc, without additional constraints. 'norecs' means the slave (child) will be empty. 'sameasnull' means to handle it as if the key value in the master were NULL (see master_null_action). - - One of: allrecs, norecs, or nullisvalue (the default). If the master osrc has a NULL value for its key (as defined by key_#/target_key_#), this setting determines how the slave (child) osrc behaves. 'allrecs' and 'norecs' have the same meaning as for master_norecs_action. 'nullisvalue' means to treat the NULL as a value, and query for slave records that have a matching NULL values in their key (as defined by key_#/target_key_#). If no slave records have null values in the key, then 'nullisvalue' and 'norecs' have the same result. - - The target objectsource that this objectsource will be related to. - - The field names in the target objectsource to be used for the key value for the relationship (where # is an integer 1 through 5). These keys can be target_key_1 through target_key_5. - - - - - - The field in the specified 'osrc' in which to store the auto number. - - (e.g. counterosrc). - - The field name in this objectsource to be used as the key. - - The osrc in which to store the next auto id / counting number. - - - - - - - - This event occurs when a query is opened and the query ID is not null. - - This event occurs when data is created in the object source. - - This event is invoked whenever the current record changes to a different record. - - This event is invoked when a query is completed and the last row(s) retrieved. - - This event occurs when data is modified in the object source. - - This event occurs when replicant entries are swapped. - - - - - - - Creates a base of an object and notifies all childeren that a child is creating an object. - - If it is called while an object is being created it stops the creation and cleans up the mess. - - Changes the data item that the object source points to in the server. - - Clears the replica of data. - - Encapsulates data in an array and calls CreateObject. - - Creates an object though OSML - - Deletes an object through OSML. - - Stops relations with all clients and forces them to resync. - - DEPRECATED: Performs a double synchronization with two other objectsources, known as the Parent and the Child, in two steps. The first step is like Sync (see below), with a ParentOSRC and ParentKey1-ParentKey9] / ParentSelfKey1-ParentSelfKey9. Next, a Sync is performed between the current objectsource and the ChildOSRC in the same way the first step performed a sync between the ParentOSRC and the current objectsource, respectively, using SelfChildKey1-SelfChildKey9 / ChildKey1-ChildKey9. - - Searches for a certain object in the replica (retrieved records), and makes it the current object. Parameters: To search by record number, set ID equal to the integer (1 = first record). To search by object name (primary key), set Name equal to a string containing the primary key (note that concatenated keys use | as a separator). To search by other abitrary field values, set those values in the parameters to this action. - - Returns first record in the replica. - - Returns last record in the replica. - - Modifies an object through OSML. - - Returns next record in the replica. - - Order Object. - - Returns previous record in the replica. - - Query. - - Query Object. Primarily for internal use only. - - Refreshes the query, and allows new parameter values to be passed in. Previous comment: Re-runs the SQL query, but adds more constraints. The additional constraints are provided as parameters to the connector which invokes this action. - - Runs the query, searching for objects whose attributes *contain* a combination of string values. 'query' contains a space-separated list of strings that must be present in each returned record (typically the 'query' is typed by the user). 'field_list' is a comma-separated list of field names to search in. Each field name (attribute) can be preceded by a * or followed by a *; the presence of these asterisks controls whether the matching is done on the entire attribute value or just as a substring match. Examples: 'my_key,*my_description*' for field_list means to match exact values for my_key, and match anywhere in my_description. cx__case_insensitive can be set to 1 to make the search case insensitive. - - Re-runs the SQL query using the previously stored parameter values (if any). This is used when the data on the server is believed to have changed, and the new data is desired to be displayed in the application. This action does not change the current row. - - Re-runs the SQL query for just the one current object (record). This does not change the currently selected record in the osrc. This should be used when the intent is to refresh the values of just the current object without affecting anything else. - - DEPRECATED: Performs a synchronization operation with another objectsource by re-running the query for this objectsource based on another objectsource's data. Used for implementing relationships between objectsources. The ParentOSRC (string) parameter specifies the name of the objectsource to sync with. Up to nine synchronization keys can be specified, as ParentKey1 and ChildKey1 through ParentKey9 and ChildKey9. The ParentKey indicates the name of the field in the ParentOSRC to sync with (probably a primary key), and ChildKey indicates the name of the field (the foreign key) in the current objectsource to match with the parent objectsource. - - Save the clients that connect and lets clients of clients know that orsc is conected to them. - - Moves the current row backwards. - - Moves the current row forwards. - - - - - - - - - -
- - - - - -

The page widget represents the HTML application (or subapplication) as a whole and serves as a top-level container for other widgets in the application. The page widget also implements some important functionality regarding the management of keypresses, focus, widget resizing, and event management. When creating an application, the top-level object in the application must either be "widget/page" or "widget/frameset", where the latter is used to create a multi-framed application containing multiple page widgets.

- -

Page widgets specify the colors for mouse, keyboard, and data focus for the application. Focus is usually indicated via the drawing of a rectangle around a widget or data item, and for a 3D-effect two colors are specified for each type of focus: a color for the top and left of the rectangle, and another color for the right and bottom of the rectangle. Mouse focus gives feedback to the user as to which widget they are pointing at (and thus which one will receive keyboard and/or data focus if the user clicks the mouse). Keyboard focus tells the user which widget will receive data entered via the keyboard. Data focus tells the user which record or data item is selected in a widget.

- -
- - - -

The page widget cannot be embedded within other widgets on a page. There must only be one per page, unless a frameset is used, in which case page widgets may be added within a frameset widget.

- -
- - - - Determines how close the window is to the browser edge. - - A background image for the page. - - The background color for the page. Can either be a recognized color (such as "red"), or an RGB color (such as "#C0C0C0"). - - A color, RGB or named, for the top and left edges of rectangles drawn to indicate data focus (default is dark blue). - - A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate data focus (default is dark blue). - - The default font to use in the application (such as "Arial"). - - The default font size, in pixels (same as points on a typical 72dpi display), for text in the application. - - The height of the page in pixels. - - When the application is rendered over HTTP/HTML, this controls the X-Frame-Options anti-clickjacking HTTP response header. Possible values are "none", "sameorigin", and "deny". The default value is set by the x_frame_options setting in the "net_http" section of centrallix.conf (see configuration docs for more information). The http_frame_options setting only applies to the page widget it is set in, not to any components that the page loads (it can be set in those components separately if needed). - - The file path to the page icon. - - A color, RGB or named, to be used for the top and left edges of rectangles drawn to indicate keyboard focus (default is "white"). - - A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate keyboard focus (default is dark grey). - - Color for hyper links. - - True if page is loaded, false otherwise. - - The maximum amount of server requests at one time. - - A color, RGB or named, to be used for the top and left edges of rectangles drawn to indicate mouse focus (default is "black"). - - A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate mouse focus (default is "black"). - - Set this to "yes" to display message boxes when application errors occur, such as an undefined property reference. The default is "no", but this is handy when debugging. - - The title which will appear in the browser's window title bar. - - The default color for text which appears on the page. Can either be a named color or RGB (numeric) color. - - One or more comma-separated pathnames for widget templates to use. The specified files should contain "widget/template" objects (see "widget/template for details). - - The width of the page in pixels. - - - - - - - - - - This occurs when the page initializes. - - This occurs when the uses right clicks on a mouse or mouse pad. - - - - - - Sends an alert widget. - - Closes the page. - - Starts a new app in a new window. - - Loads the page. - - - - - - - - - -
- - - - - -

The pane is Centrallix's simplest container. It consists only of a background and a border, which can either have a "raised" edge or "lowered" edge style.

- -
- - - -

This container can be placed inside any widget having a visible container. Visual or nonvisual widgets may be placed inside a pane.

- -
- - - - A background image for the pane. - - A color, RGB or named, to be used as the pane's background. If neither bgcolor nor background is specified, the pane will be transparent. - - The radius (sharpness) of the pane, smaller is more sharp. - - For "bordered" pane styles, this is the color of the border, either named or a #number. - - Height, in pixels, of the pane. - - The color of the shadow. - - The placement of the shadow with respect to the window. - - A radius that describes the sharpness of the corners of the shadow (smaller means sharper). - - "raised", "lowered", "bordered", or "flat". Determines the style of the pane's border. - - Width, in pixels, of the pane. - - X-coordinate of the upper left corner of the pane, relative to its container. - - Y-coordinate of the upper left corner of the pane, relative to its container. - - - - - - - - Indicates if the user can interact with the pane (if so "true"). - - - - - - This event occurs when the user clicks the checkbox. No parameters are available from this event. - - This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the checkbox. - - This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. - - This event occurs when the user releases the mouse button on the checkbox. - - - - - - Creates a triangular pointer on the edge of a pane to point at a given (X,Y) coordinate. - - Changes the width and height of the pane to the given (Width,Height). - - Sets the backgound image or color of the pane, using the attribute Color or Image. - - - - - - - - - -
- - - - - -

Applications (.app) and components (.cmp) can accept parameters, which can then be used to control the way that the application or component is rendered or the way it functions. The widget/parameter widget is used to declare a parameter on an application or component, and to specify data type, default values, and more. ObjectSource widgets can also have parameters, which are used when running the ObjectSource's SQL query.

- -
- - - -

Parameters must be placed inside a widget/page, widget/component-decl, or widget/osrc. They cannot be used anywhere else, and cannot contain other widgets.

- -

Parameters have a very similar set of properties to the widget/hints widget, which is used to attach "presentation hints" to a widget.

- -

To use a parameter value, just reference it in a runserver() expression with object "this", as in :this:parametername. For "object" parameters, reference them by just using the parameter name as if you would use the name of the object itself if the object were physically present in the component instead of being passed as a parameter (see sample code).

- -
- - - - Parameter widgets are treated as other widgets and normally would appear as widgets in the namespace on the client, with the parameter values being available in runclient() expressions on the client. For efficiency reasons, however, parameters to static components, other than parameters of type "object", are not deployed to the client. To override this behavior, set this option to "yes". - - If this is a parameter to a component, and the parameter has type "object", this can be set to a type of widget that should be searched for in the containing application or component once the component is instantiated. Note that the object being searched for must be a container of the component instance, either directly or indirectly. This option is frequently used to link in with a form or objectsource in the contianing application or component, without that form or objectsource having to be explicitly passed to the component. - - Gives the name of the parameter being passed, thus allowing the parameter widget name (which must be unique within the application or component) to be different from the parameter name as referenced within expressions, SQL, or connectors. - - The data type of the parameter. Can be "integer", "string", "double", "datetime", "money", or "object". - - - - - - This sets the value of the parameter. - - - - - - This event occurs when the parameter being passed is changed. - - - - - - - - - -
- - - - - -

A radio button panel widget is a form element used to create a set of radio buttons on screen. Only one radio button may be selected at a time. When the form goes into query mode the radio buttons change into checkboxes and more than one can be selected at a time.

- -
- - - -

The radio button panel can be placed inside of any visual container, and will automatically attach itself to a form widget if it is inside of one (directly or indirectly). The "widget/radiobuttonpanel" is the main widget, and can contain any number of "widget/radiobutton" widgets which specify the choices which will be present on the panel. No other visual widgets can be contained within a radio button panel.

- -

Note: form widget interaction was not yet implemented as of the time of writing of this document.

- -
- - - - Name of the column in the datasource you want to reference. - - A background image for the radio button panel. - - A color, RGB or named, for the panel background. If neither bgcolor nor background transparent. - - Height, in pixels, of the panel. - - An image to be used for the rectangular border drawn around the radio buttons. - - The color, RGB or named, of the text within the panel. Default: "black". - - The title for the radio button panel, which appears superimposed on the rectangular border around the radio buttons. - - Width, in pixels, of the panel. - - X-coordinate of the upper left corner of the panel, default is 0. - - Y-coordinate of the upper left corner of the panel, default is 0. - - - - - - - - The text label to appear beside the radio button. - - the radio button is initially selected or not. Should only be set on one radio Default;"false". - - The value of the selected item. - - - - - - - - - - - -
- - - - - -

The remote control nonvisual widget allows for one application (or instance of an application) to activate Actions in another running application, even if those applications are on two separate client computer systems. This is done by passing the event/action information through a remote control channel on the server.

- -

Two remote control widgets are required: a master and slave. This widget is the slave widget, which receives remote control events via the Centrallix server. When a master widget (remotemgr) sends an event through the channel, this slave widget is automatically activated and can then trigger the appropriate action on another widget on the page.

- -

In order for the remote control event to be passed through Centrallix, the master and slave widgets must both be using the same channel id and be logged in with the same username.They need not be a part of the same session on the server.

- -

Note: at the time of this writing, the remotectl widget was not yet operational.

- -
- - - -

The remote control widget is a nonvisual widget and thus cannot contain visual widgets. It is normally located at the top-level of an application, within a "widget/page" object.

- -
- - - - - - - -
- - - - - -

The remote control manager nonvisual widget allows for one application (or instance of an application) to activate Actions in another running application, even if those applications are on two separate client computer systems. This is done by passing the event/action information through a remote control channel on the server.

- -

Two remote control widgets are required: a master and slave. This widget is the master widget, which sends remote control events via the Centrallix server. When a this widget sends an event through the channel, the slave widget (remotectl) is automatically activated and can then trigger the appropriate action on another widget on the remote application's page.

- -

In order for the remote control event to be passed through Centrallix, the master and slave widgets must both be using the same channel id and be logged in with the same username.They need not be a part of the same session on the server.

- -

Note: at the time of this writing, the remotemgr widget was not yet written.

- -
- - - -

The remote control manager widget is a nonvisual widget and thus cannot contain visual widgets.It is normally located at the top-level of an application, within a "widget/page" object.

- -
- - - - - - - -
- - - - - -

The 'repeat' nonvisual widget is used to repeat its entire subtree of widgets for each record in an sql query.

- -
- - - -

This widget has no content of its own, so it is only useful if it has widgets inside it. For positioning of visual widgets inside a widget/repeat, an hbox or vbox (outside the widget/repeat) can be used, or the x and y can be set mathematically based on results from the SQL query.

- -

The widget/repeat can be useful in creating data-driven user interfaces, as well as in facilitating a plug-in architecture in your application. For instance, the SQL query could retrieve a list of matching components to be included in an interface, and the repeat widget could create components, tabs, windows, table columns, buttons, etc., for each returned SQL query record.

- -
- - - - The sql query that will be run on the server. - - - - - - - - - -
- - - - - -

The rule widget is used as an important part of Centrallix's declarative application development. It specifies behavior expected from a widget or relationship between two widgets. For instance, one type of rule, the osrc_relationship rule, ties two objectsources together to enforce a primary key / foreign key relationship between the two.

- -
- - - -

Various widgets can have rule widgets; the various types of rule widgets are described in the sections for the widgets that they relate to.

- -
- - - - The type of rule. For instance, a "widget/osrc" can have "osrc_relationship" and "osrc_key" rules. - - - -
- - - - - -

The scrollbar is used to allow the uesr to control a numeric value; typically the scrollbar is tied to the scrolling behavior of another widget.

- -

Currently, both table and scrollpane widgets have their own scrollbars, so this widget is not used for either of those.

- -
- - - -

As a visual widget, the scrollbar can be placed anywhere a visual widget is permitted.

- -
- - - - A background image to be placed behind the scrollbar. - - A color, RGB or named, to be used as the scrollbar background. If neither bgcolor nor background is supplied, the scrollbar is transparent. - - Either "horizontal" or "vertical" indicating the visible direction that the scrollbar is oriented. - - Height, in pixels, of the scrollbar. - - The upper limit of the range of the scrollbar's value. The value will range from 0 to the number specified by 'range'. This property can be set to a runclient() dynamic expression, in which case the range will change as the expression changes. - - This acts as a boolean to denote if the scroll bar is visible ("true" is visible). - - Width, in pixels, of the scrollbar. - - X-coordinate of the upper left corner of the scrollbar, relative to its container. - - Y-coordinate of the upper left corner of the scrollbar, relative to its container. - - - - - - Sets the scroll bar to a specific location determined by the parameter. - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - -
- - - - - -

The scrollpane widget provides a container and a scrollbar. The scrollbar can be used to move up and down in the container, so more content can be placed in the container than can be normally viewed at one time.

- -

The scrollbar includes a draggable thumb as well as up and down arrows at the top and bottom. Clicking the arrows scrolls the content of the container up or down by a small amount, whereas clicking on the scrollbar itself above or below the thumb will scroll the area by a large amount.

- -
- - - -

Scrollpane widgets can be placed inside any other container, but are usually placed inside a pane or a tab page. Almost any content can be placed inside a scrollpane, but most commonly tables, treeviews, and html areas appear there.

- -
- - - - A background image to be placed behind the scrollpane. - - A color, RGB or named, to be used as the scrollpane background.If neither bgcolor transparent. - - height, in pixels, of the scrollpane's visible area. Due to the nature of the can time. - - allows user to set scroll pane to visible (true) or not (false). - - width, in pixels, of the scrollpane, including the scrollbar area on the right side. - - x-coordinate of the upper left corner of the scrollpane, relative to its container. - - y-coordinate of the upper left corner of the scrollpane, relative to its container. - - - - - - Scrolls to a specific location determined by the scroll bar. - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - - - - - - - - -
- - - - - -

The TabControl widget provides a DHTML tab control within Centrallix. The widget behaves in the same way as tab controls in other GUI environments, providing a set of tab pages, layered one on top of the other, which can be selected (brought to the foreground) by clicking the mouse on the respective visible tab at the top of the tab control.

- -

To further distinguish which tab at the top of the tab control is active, this widget slightly modifies the X/Y position of the tab as well as changing a thumbnail image (on the left edge of the tab) to further enhance the distinction between selected and inactive tab pages.

- -
- - - -

The tab pages are containers, and as such, controls of various kinds, including other tab controls, can be placed inside the tab pages.

- -

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", with the exception of nonvisuals such as connectors.

- -

Tab pages also have a 'visible' property which allows them to be hidden and revealed. This is used if the type is set to dynamic, but can be used manually as well.

- -
- - - - An image to be used as the background of the tab control. - - As an alternate to "background", "bgcolor" can specify a color, either named or RGB. - - A color that outlines window. - - A radius that describes the sharpness of the corners of the window (smaller means sharper). - - Determines the look of the outline. - - The height, in pixels, of the tab control, including the page height but not including the height of the tabs at the top. - - An image to be used as the background of the tabs which are inactive (in the background). - - As an alternate to "inactive_background", "inactive_bgcolor" can specify a color, either named or RGB. - - The placement of the shadow described as a rotational transformation with respect to the window. - - The color of the shadow. - - The placement of the shadow with respect to the window. - - A radius that describes the sharpness of the corners of the shadow (smaller means sharper). - - The location of the tabs: "top" (default), "bottom", "left", "right", or "none". - - The width of the tabs in pixels. This is optional for tab_locations of "top", "bottom", and "none". - - The color of the text to be used on the tabs to identify them. - - allows user to set tab to visible (true) or not (false). - - Width, in pixels, of the tab control, including the page width but not the width of the tabs (if they are at the side). - - X-coordinate of the upper left corner of the tab control, relative to the container. - - Y-coordinate of the upper left corner of the control, relative to its container. - - - - - - - - This is the fieldname from the objectsource that the tabpage will use for its title. - - The name of the tab page, which appears in the tab at the top of the control. - - If this is set to dynamic then the tabpage will act as an objectsource client, displaying zero or more tabs: one tab for each item in the replica with title set to the value of the fieldname. In this way, a tab control can have a mix of dynamic tabs and static ones. - - 0 or 1. Can contain a dynamic runclient() expression to control when the tab page is visible to the user. - - - - - - - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - This event occurs when the visible tab changes. - - - - - - The name of the tab page that should be initially selected. This can also contain a dynamic runclient() expression controlling which tab page is selected. - - Similar to "selected", but selects the tab by numeric index. - - - - - - Sets the selected tab according to the parameter given (the tab itself or its index). - - - - - - - - - -
- - - - - -

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data.The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

- - Table widgets come in three different flavors: static, dynamicpage, and dynamicrow.Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget.Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will.Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query.As of the time of writing of this document, only static mode and dynamicrow mode were supported. - -

Table widgets allow the selection (keyboard, mouse, and data focus) of individual rows.

- -
- - - -

Table widgets are normally placed inside of a scrollpane so that any rows which don't fit int the container can still be viewed. Table columns are created via "widget/table-column" child widgets within the table widget.

- -
- - - - Whether to permit the user to select rows in the table. Default "yes". - - A background image for the table. This "shows through" between table cells. - - A color, RGB or named, to be used between table cells and rows.If neither bgcolor nor background are specified, the table is transparent. - - The horizontal spacing between cells in the table, in pixels. Default is 1. - - The vertical spacing between cells in the table, in pixels. Default is 1. - - The width of the column separation lines in pixels. Default is 1. - - Either "rows" (default) or "properties". In "properties" mode, the table displays one row per attribute, and so only displays the current record in the objectsource. In "rows" mode, the table displays one row per record in the objectsource. - - Acts as a boolean to only show the scrollbar when it is needed (activates when it is set to "1"). - - Whether to allow dragging of column boundaries to resize columns; set to 1 to allow it and 0 to disallow. Default is 1. - - Set to 'yes' to cause the table's current row to follow the currently selected object in the ObjectSource, and 'no' to disable this behavior. Default is 'yes'. - - Whether to show the table's grid in rows which do not hold data. Set to 1 to show the grid or 0 to not show it; default is 1. - - A background image for the header row cells. - - A color, RGB or named, for the header row cells. - - Acts as a boolean to determine if the scrollbar can be seen (hide is "1"). - - The height in pixels of the table. - - The maximum height of the image from the child. - - The maximum width of the image from the child. - - Sets which data element in the table has focus initially. - - width of the inner spacing between cells in a table. Default0. - - margins within each cell, in pixels. Default is 0 pixels. - - The maximum height of each row. - - The minimum height of each row. - - A background image for the "new row" placeholder that is visible when a new object is being created. - - A color, RGB or named, for the "new row" placeholder that is visible when a new object is being created. - - Identifies the OSRC that the table connects to. - - width of the outer spacing around the outside of the table, in pixels. Default0. - - Acts as a boolean to allow the scroll bar to overlab with the table (allow is "1"). - - Acts as a boolean to reverse to order of the table elements (reverses if set to "1"). - - A background image for the table row cells. - - A color, RGB or named, for the table row cells. - - A background image for the table row cells. If this is specified, rows will alternate in backgrounds between "row_background1" and "row_background2". - - A color, RGB or named, for the table row cells. If this is specified, rows will alternate in colors between "row_bgcolor1" and "row_bgcolor2". - - Determines the color of the edge of the rows. - - Determines the sharpness of the row corners, smaller is more sharp. - - How many rows are shown in the table. - - The height of the individual rows in pixels. Default is 15 pixels. - - A background image for the current (selected) row cells. - - A color, RGB or named, for the current (selected) row cells. - - The color of the shadow of the row. - - How far the shadow is from the row. - - The sharpness of the corners of the shadow, smaller means more sharp. - - Whether to highlight the currently selected row. Default "yes". - - A color, RGB or named, of the text in the normal data rows. - - A color, RGB or named, of the text in the "new row" placeholder. - - A color, RGB or named, of the text in the highlighted data row. - - Whether to show the title bar of the table. Default "yes". - - A color, RGB or named, of the text in the header row. If unset, defaults to textcolor. - - Width, in pixels, of the table.The height is determined dynamically. - - The maximum number of rows to show at any given time, for dynamic tables. - - X-coordinate of the upper left corner of the table. Default is 0. - - Y-coordinate of the upper left corner of the table. Default is 0. - - - - - - - - The alignment of the column: "left" or "right". - - It is a pseudonym for the fieldname. - - The color of the caption_fieldname. - - Acts as a boolean to determine if items are group able. - - The height in pixels of the column. - - The title of the column to be displayed in the header row. - - The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. - - width of the column. - - Determines if text can wrap around an obstacle. - - - - - - - - The Click event fires when a user clicks on a row. - - The DblClick event fires when the user double-clicks the mouse on a row. Passes three values, 'Caller' which is the table's name, 'recnum' which is the sequential number of the record in the table, and 'data' which is an array of the data in the selected record. - - The Click event fires when a user right clicks on a mouse or mouse pad. - - - - - - Drops all elements from the table. - - - - - - - - - -
- - - - - - - - - -

A widget/template must be a root widget of a file (which normally is given a .tpl extension).

- -

Each child in a widget/template is a "rule". Each rule applies to widgets that both 1) have the same 'widget_class' property value (there can be only one widget_class per widget (and "rule")) and 2) match the widget type of the child (eg "widget/imagebutton").

- -

Every other property of the "rule" are default values.

- -

All children of the "rule" are automatically inserted into the matched widgets.

- -
- - - - - - - - The value of this property must match another widget before said widget will "inherit" this "rule". - - - - - - - - - - 1); - - cnFirst "widget/connector" { event="Click"; target=template_form; action="First"; } - - } - - } - - ]]> - - - -
- - - - - -

The textarea is a multi-line text edit widget, allowing the user to enter text data containing line endings, tabs, and more. It also contains a scrollbar which allows more text to be edited than can fit in the displayable area.

- -
- - - -

The textarea is a visual form element widget, and can be contained inside any container capable of holding visual widgets. It may only contain nonvisual widgets like the connector.

- -
- - - - A background image for the textarea. - - A color, RGB or named, to be used as the background. If neither bgcolor nor background are specified, the textarea is transparent. - - The field in the objectsource to associate this textarea with. - - Links text area to a form. - - The height of the textarea, in pixels - - The maximum number of characters the textarea should accept from the user - - Can hold text, html, or wiki and display. - - Set to 'yes' if the data in the text area should be viewed only and not be modified. - - The border style of the text area, can be 'raised' or 'lowered'. Default is 'raised'. - - The width of the textarea, in pixels - - The horizontal coordinate of the left edge of the textarea, relative to the container. - - The vertical coordinate of the top edge of the textarea, relative to the container. - - - - - - - - This event occurs before the key press event is fired and can stop the key press event. - - This event occurs when the user has modified the data value of the widget (clicked or unclicked it). - - This event occurs when the data is changed (occurs when key press or button changes things). - - This event occurs when the user presses the escape key. - - This event occurs when the editbox receives keyboard focus (if the user tabs on to it or clicks on it, for instance). - - This event occurs when the user presses any key. - - This event occurs when the editbox loses keyboard focus (if the user tabs off of it, for instance). - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - This event occurs when the user presses the tab key. - - - - - - Checks to see if things need to be changed (new is different from old) then calls set value. - - Sets the focus on a selected widget. - - Sets the content to the text parameter sent. - - - - - - - - - - - -
- - - - - -

A textbutton provides similar functionality to the imagebutton. However, the programmer need not create two or three graphics images in order to use a textbutton; rather simply specifying the text to appear on the button is sufficient.

- -

Textbuttons, like imagebuttons, can either have two or three states. A three-state textbutton doesn't have a "raised" border until the user points to it, whereas a two-state textbutton retains its raised border whether pointed to or not.

- -
- - - -

The TextButton can be placed inside any visible container, but only nonvisual widgets can be placed within it.

- -
- - - - Sets the alignment of text in the button, can have left right or center (default). - - A background image for the button. - - A color, RGB or named, to be used as the button's background.If neither bgcolor nor background are specified, the button is transparent. - - A color that outlines the button. - - A radius that describes the sharpness of the corners of the button (smaller means sharper). - - Determines the look of the outline. - - A color, RGB or named, to be used for the button's text when it is disabled. - - Whether the button is enabled (can be clicked). Default is 'yes'. Also supports dynamic runclient() expressions allowing the enabled status of the button to follow the value of an expression. - - A color, RGB or named, for the text on the button. Default "white". - - A color, RGB or named, for the text's 1-pixel drop-shadow. Default "black". - - Height, in pixels, of the text button. - - File path to the source of the image. - - Defines the height of image. - - Defines spacing between image and the border. - - Defines the width of image. - - Describes where an image is in the text button (top-default, right, bottom, left). - - The text to appear on the button. This may be a dynamic runclient() expression to dynamically change the button's text. - - Whether or not the button is tri-state (does not display a raised border until the user points at it). Default is yes. - - The width, in pixels, of the text button. - - X-coordinate of the upper left corner of the button, relative to its container. - - Y-coordinate of the upper left corner of the button, relative to its container. - - - - - - - - - - This event occurs when the user clicks the button. No parameters are available from this event. - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - - - - - Called from a connector, this action sets the button's text to the value passed in through connector's "Text" parameter. - - - - - - - - - -
- - - - - -

A timer widget is used to schedule an Event to occur after a specified amount of time. Timers can be set to expire a set amount of time after the page is loaded, or they can be triggered into counting down via activating an Action on the timer. Timers can be used to create animations, delayed effects, and more.

- -
- - - -

Timers are nonvisual widgets which can be placed almost anywhere in an application. They are most commonly found at the top-level of the application, however. Timers have no direct effects on the object in which they are placed. Timers can only contain Connector widgets.

- -
- - - - The timer starts counting down again immediately after it expires. - - The timer starts counting down immediately after the page loads. - - Of milliseconds (1/1000th of a second) before the timer expires. - - - - - - Actions causes a timer to stop counting down, and thus no Expire event will occur until another countdown sequence is initiated by a SetTimer action. - - Action takes two parameters: "Time" (integer in milliseconds) and "AutoReset" (integer 0 or 1). It causes a timer to begin counting down towards an Expire event. - - - - - - This occurs when the timer hits its timeout and the auto reset is not enabled. - - - - - - - - - -
- - - - - -

A treeview provides a way of viewing hierarchically-organized data via a "traditional" GUI point-and-click tree structure. A treeview has "branches" that can expand and collapse entire subtrees of data.

- -

Centrallix treeviews present a subtree of the ObjectSystem, with the data dynamically loaded, on demand, from the server as the widget is used. The treeview widget thus has no intelligence in and of itself in determining what kinds of objects are presented at each level of the tree. Many times, this is exactly what is desired because the treeview is being used to simply browse objects in the ObjectSystem, such as directories and files. In other cases, the treeview is teamed up with a special ObjectSystem object called a "querytree" (QYT) object. The querytree object creates a hierarchical view from other potentially non-hierarchical data in the ObjectSystem, such as that from different database tables and so forth.

- -
- - - -

Treeviews can be placed inside of any visual container, but are usually placed inside of a scrollpane, since scrollpanes can expand to allow the user to view data that would not normally fit inside the desired container. Treeviews can contain only nonvisual widgets such as connectors.

- -
- - - - A color, RGB or named, to be used for the text for the items in the treeview. - - A background image for the selected item in the treeview. - - A color, RGB or named, to be used for the text for the selected item. - - A pathname to an image to be used as icons for the items in the treeview. - - Orders treeview in descending (desc) and ascending (anything else) order. - - Whether to display the connections between items in the tree using lines. - - Whether to display the root of the tree. If "no", then the root is auto-expanded and only its children are visible. - - Whether to display the connection lines between the root and the root's immediate children. If "no", the root's children are flush against the lefthand side of the treeview, otherwise the connecting lines show up. - - The ObjectSystem path of the root of the treeview. - - If set to "yes", the branch lines are drawn in a 3D style, otherwise they are drawn in a single color. - - Width, in pixels, of the treeview. - - X-coordinate of the upper left corner of the treeview's root object. - - Y-coordinate of the upper left corner of the treeview's root object. - - - - - - This event occurs when the user clicks while the treeview is in focus. - - Occurs when the user clicks on the clickable link for an item in the treeview. Its one parameter is "Pathname", or the ObjectSystem path to the object which was selected. - - This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. - - This event occurs when the user moves the mouse pointer off of the widget. - - This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. - - This event occurs when the user releases the mouse button on the widget. - - This event occurs when one of the elements of the treeview is given focus. - - To the above, but when the user right-clicks on an item in the treeview. - - - - - - Searches for a specific element. - - Returns the next element in the treeview. - - Transfers focus to the selected element. - - Sets the selected element as the root. - - - - - - - - - -
- - - - - -

The 'variable' nonvisual widget is used to create a global javascript scalar variable in the application.The variable can be an integer or string.

- -
- - - -

This nonvisual widget is used at the top-level (within a "widget/page"). Currently it has no events, and so shouldn't contain any visual or nonvisual widgets.

- -
- - - - The field with which to link / connect to the variable. - - The form (e.g. it's osrc) with which to associate the variable and fieldname (if different than the form in which variable is currently nested). - - Can be accessed as :var_name:value. - - - - - - Called by a widget/connector: Sets the value of the variable to whatever is specified by the "Value" parameter. - - - - - - This event occurs when the user has modified the data value of the widget (clicked or unclicked it). - - This event occurs when the data is changed (occurs when key press or button changes things). - - - - - - - - - -
- - - - - -

An autolayout widget with style set to "vbox". See "widget/autolayout".

- -
- -
- - - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + +

The autolayout widget is used to automatically position widgets without having to explicitly provide x, y, width, and height values for those widgets. Most of the layout in Centrallix is done on a coordinate basis (although the automatic resize logic adjusts those coordinates to fit the target application or component render size). The autolayout widget provides the capability to do nested gui layout in Centrallix, where the coordinate positions of the widgets are computed automatically.

+ +

Normally, the autolayout widget is used not as "widget/autolayout", but as "widget/hbox" or "widget/vbox", two synonyms for this widget which also set the layout direction (horizontal or vertical).

+ +

A "hbox" widget stacks its children horizontally one after the other, while a "vbox" widget stacks its children vertically from the top down. The spacing between children can be set, and the size of each child can be either independently set per-child, or it can be set by virtue of the geometry of the autolayout widget.

+ +

The autolayout widget supports having multiple rows or columns of widgets. That is, if a "vbox" widget fills up vertically, it can resume adding children to a second vertical column should there be space to the right of the original column of widgets. For this to work, the width of the columns (for a vbox) or the height of the rows (for an hbox) must be specified.

+ +
+ + + +

The autolayout widget can occur any place that a visual widget can be placed. As it is a container, it can also contain both visual and nonvisual widgets and containers.

+ +
+ + + + Sets the alignment of text can have left (default) right or center. + + For an hbox, this is the width to use for the child widgets (unless otherwise specified by the child widget). For a vbox, this refers to the height to use for child widgets unless otherwise specified. + + This specifies the width of columns for a vbox -- should the first column of widgets fill up, a second column will be started at this offset from the start of the first column. + + Height, in pixels, of the autolayout area. If omitted, it defaults to the maximum available height for the given width, without overlapping other visible widgets. If both width and height are unspecified, Centrallix will chose a width and height that maximize the available autolayout area. + + Makes the text fill page (justifies text - google it if you need). + + This specifies the height of rows for an hbox -- should the first row of widgets fill up horizontally, a second row will be started beneath the first one, at this vertical offset from the start of the first row. + + The spacing, in pixels, between widgets. For an hbox, this refers to the horizontal spacing between children. For a vbox, this refers to the vertical spacing. + + Either "hbox" or "vbox". Not needed if the widget is created via "widget/hbox" or "widget/vbox". + + X-coordinate of the upper left corner of the autolayout area. + + Y-coordinate of the upper left corner of the autolayout area. + + Width, in pixels, of the autolayout area. If omitted, it defaults to the maximum available width for the given height, without overlapping other visible widgets. + + + + + + + + An optional property, this allows the layout sequence of child widgets to be controlled, and the children are placed in ascending order. If unspecified, this defaults to a value of 100 for the first child encountered, or to N+1 where N is the autolayout sequence of the most recent child widget encountered. + + Height, in pixels, of the child. + + Width, in pixels, of the child. + + + + + + + + + + + + + +
+ + + + + +

Combines the functionality of the textbutton and imagebutton, as well as adding the ability to have both text and image displayed in a button.

+ +
+ + + +

A Button can be placed inside any visible container, but only nonvisual widgets can be placed within it. Properties that don't apply to the button's type are ignored.

+ +
+ + + + A color, RGB or named, to be used as the button's background.If neither bgcolor nor background are specified, the button is transparent. + + The ObjectSystem pathname of the image to be shown when the user clicks the imagebutton. Defaults to 'image' if not specified. + + A color, RGB or named, to be used for the button's text when it is disabled. + + The ObjectSystem pathname of the image to be shown when the imagebutton is disabled. Defaults to 'image' if not specified. + + Whether the button is enabled (can be clicked). Default is 'yes'. Also supports dynamic runclient() expressions allowing the enabled status of the button to follow the value of an expression. + + A color, RGB or named, for the text on the button. Default "white". + + A color, RGB or named, for the text's 1-pixel drop-shadow. Default "black". + + Height, in pixels, of the text button. + + The pathname of the image to be shown when the button is "idle". + + The pathname of the image to be shown when the button is pointed-to. Defaults to the 'image' if not specified. + + The distance between the image and text if applicable. + + The text to appear on the button. + + Whether or not the button is tri-state (does not display a raised border until the user points at it). Default is yes. + + There are currently 7 different types. 1) text 2) image 3) topimage (image above text) 4) rightimage 5) leftimage 6) bottomimage 7) textoverimage (text over image background). + + The width, in pixels, of the text button. + + X-coordinate of the upper left corner of the button, relative to its container. + + Y-coordinate of the upper left corner of the button, relative to its container. + + + + + + + + This event occurs when the user clicks the widget. No parameters are available from this event. + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user releases the mouse button on the widget. + + + + + + + + + + + +
+ + + + + +

The calendar widget is used to present event/schedule types of data in a year, month, week, or day format. The data for the events on the calendar is obtained from an objectsource's query results, and as such, can be controlled via queries and other forms/tables associated with the same objectsource. The four formats are listed below and can be selected during app run-time.

+ +
    + +
  • Year - Presents twelve months at a time in a low-detail type of setting; when events occur on a day, the day is highlighted, but no more data than that is displayed. If the user points the mouse at a given day, it will display in a popup "tooltip" type of manner the events associated with that day. If more than one year's worth of data is in the objectsource's replica, then multiple years are displayed one after the other. Each year is displayed as four rows of three months each, in traditional calendar format.
  • + +
  • Month - Presents one month's worth of data in a medium-detail display. For each month with data in the objectsource, the calendar will display the month as multiple rows, each row containing one week's worth of days. The calendar will attempt to display the various entries for each day, in order of priority, but will limit the amount of data displayed to keep the boxes for the days approximately visually square. Pointing at a day will show the events for the day in a popup, and pointing at an event in the day will show the details for that event.
  • + +
  • Week - Presents one week's worth of data in a high-detail display. For each week with data in the objectsource, the calendar will display the week as seven days with a large vertical extent capable of displaying all events for the day. The left edge of the display will contain times during the day for the day's events to line up with.
  • + +
  • Day - Presents an entire day's worth of data in a high-detail display. For each day with data in the objectsource, the calendar will display the day in full-width, with the day's events listed chronologically from top to bottom.
  • + +
+ +

It should be noted that all of the data for the calendar must fit into the objectsource, or else the calendar will not display all of the available data. In this manner the calendar's display must be controlled via the objectsource's data.

+ +
+ + + +

The calendar widget can be placed inside of any visual container, but because its height can change, it is often placed inside of a scrollpane widget. This widget may not contain other visual widgets.

+ +
+ + + + An image for the background of the calendar widget. Should be an ObjectSystem path. + + A color, either named or numeric (RGB), for the background of the calendar. If neither bgcolor nor background are specified, the calendar's background is transparent. + + The visual display mode of the calendar - can be 'year', 'month', 'week', or 'day' (see the overview, above, for details on these modes). Defaults to 'year'. + + The name of the date/time field in the datasource containing the event date/time. A mandatory field. + + The name of the string field in the datasource containing the event's full description. If not supplied, descriptions will be omitted. + + The name of the string field in the datasource containing the event's short name. A mandatory field. + + The name of the integer field in the datasource containing the event's priority. If not supplied, priority checking is disabled. + + The height of the calendar, in pixels. If not specified, the calendar may grow vertically an indefinite amount in order to display its given data. If specified, however, the calendar will be limited to that size. + + The minimum priority level for events that will be displayed on the calendar; the values for this integer depend on the nature of the 'priority' field in the data source; only events with priority values numerically higher than or equal to this minimum priority will be displayed. Defaults to 0. + + The color of the words in the calendar. + + The width of the calendar, in pixels. + + X-coordinate of the upper left corner of the calendar, default is 0. + + Y-coordinate of the upper left corner of the calendar, default is 0. + + + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + + + + + + + + + +
+ + + +

A chart widget is used to display charts and graphs of data. It uses the Chart.js library.

+
+ + +

The chart widget can be placed inside of any visual container, and will attach itself to any objectsource widget that contains it (whether directly or indirectly). Charts may not contain visual widgets.

+
+ + + Currently supported types are "bar", "line", "scatter", and "pie". Defaults to "bar". Chart.js supports much more than this, so new types could be added with relative ease. + The position of the legend on the chart. May be "top", "botton", "left", or "right". + The name of the ObjectSource widget which will supply data for the chart. We recommend that you do not specify this directly but instead embed the chart widget within a parent ObjectSource, directly or indirectly. If not specified, the chart will look for an ObjectSource in its parents. + Set to false if you want the y axis to start at the lowest data value. Default is true. + The title of the chart. Default is none. + A color, RGB or named, of the chart title. + The size, in points, of the title font. + + + + + Chart.js has limited support for having multiple chart types on the same axes. The outer chart type must still be specified if this is set. + A color, RGB or named, for the chart element (ex: line) which represents this series. + Set to false to remove the color fill beneath a line chart. Default is true. + A label for the data series. + Which column in the data should be used for the x axis? By default, the chart will pick one for you. + Which column in the data should be used for the y axis? By default, the chart will pick one for you. + + + + Which axis is this? Possible values are "x" and "y". Default is "x". + A label for the axis. Default is none. + + + + + + + + +
+ + + + + +

The checkbox widget is used to display a value that can be either true or false (or on/off, yes/no, etc.).It displays as a simple clickable check box.

+ +
+ + + +

The checkbox widget can be placed inside of any visual container, and will attach itself to any form widget that contains it (whether directly or indirectly). Checkboxes may not contain visual widgets.

+ +
+ + + + Whether this checkbox should be initially checked or not. Default 'no'. + + This allows the checkbox to be changed. + + Name of objectsource field that should be associated with this checkbox. + + The name of the form that this checkbox is associated with. + + X-coordinate of the upper left corner of the checkbox, default is 0. + + Y-coordinate of the upper left corner of the checkbox, default is 0. + + + + + + + + This event occurs when the user clicks the checkbox. No parameters are available from this event. + + This event occurs when the user has modified the data value of the checkbox (clicked or unclicked it). + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + This takes a given value and sets the check box value to it. + + + + + + + + + + + +
+ + + + + +

The childwindow provides the capability of creating a popup dialog or application window within a web page. The window is not actually a separate browser window, but rather a movable container that can appear "floating" above the rest of the Centrallix application.They can take on one of two styles;"dialog" and "window", which currently relate only to how the window border and titlebar are drawn. The windows support windowshading (double-clicking on the titlebar to "rollup" or "rolldown" the window contents).

+ +

These "windows" currently do not support multiple instantiation.

+ +
+ + + +

Childwindows are normally coded at the top-level of an application, since if they are placed within containers, the container limits where the window can be moved (the window is clipped by its container). However, if the "toplevel" property is enabled, then the window floats at the top level of other widgets regardless of the size and location of its container in the application or component (thus the window is not clipped by its container).

+ +

These windows can contain other visual and nonvisual widgets.

+ +
+ + + + A background image for the body of the window. + + A color, RGB or named, to be used as the window body's background.If neither transparent. + + A color that outlines window. + + A radius that describes the sharpness of the corners of the window (smaller means sharper). + + Determines the look of the outline. + + Decides wither the screen closes widthwise (1) or heightwise (2). + + Acts as a boolean to declare if the window is shaded or not (all but title bar minimized). + + A background image for the titlebar of the window. + + A color, RGB or named, for the titlebar of the window. + + Height, in pixels, of the window. + + A pathname to an icon to be used in the upper left corner of the window. If unspecified, the default "CX" ichthus icon is used instead. + + If "yes", the window is modal (and IsModal need not be passed to "Open"). A modal window will force the user to close the window before anything else in the application can be accessed. + + The placement of the shadow described as a rotational transformation with respect to the window. + + The color of the shadow. + + A radius that describes the sharpness of the corners of the shadow (smaller means sharper). + + The placement of the shadow with respect to the window. + + Either "dialog" or "window", and determines the style of the window's border. + + The color for the titlebar's text (window title). Default "black". + + The window's title. + + Whether the window will have a titlebar (and the close "X" in the upper right corner of the window). Default "yes". + + If "yes", the window will float above all other widgets in the application, otherwise it will be clipped by its own container. + + The window is initially visible on screen. The window has an action which can "true". + + Width, in pixels, of the window. + + X-coordinate of the upper left corner of the window, relative to its container. + + Y-coordinate of the upper left corner of the window, relative to its container. + + + + + + Closes the window. Note that widgets inside the window may choose to veto the Close operation: for example, if there is unsaved data in a form. + + Opens the window. If the parameter IsModal is set to 1, then the window becomes modal (only the window's contents are accessible to the user until the window is closed). If the parameter NoClose is set to 1, then the close button in the upper right corner of the window becomes inactive and the window will only close via the Close, SetVisibility, and ToggleVisibility actions. + + Makes the window relocate to a side using a triangle (pop over). + + Opens a window like a pop-up. + + One parameter, "is_visible", which is set to 0 or 1 to hide or show the window, respectively. + + Turns the gshade property(see above) on. + + If the window is visible, this closes it. If it is closed, this opens it. + + Turns the gshade property(see above) off. + + + + + + This event is triggered each time the window is closed (becomes invisible). + + This event is triggered the first time the window is opened. If the window is visible by default, then this event is triggered when the application loads. + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + This event is triggered each time the window is opened (becomes visible). + + + + + + + + + + +
+ + + + + +

The clock widget displays the current time on the client computer, and is very configurable in terms of appearance.

+ +
+ + + +

The clock widget can be used inside of any container capable of having visual subwidgets. It may contain no widgets other than any applicable connectors.

+ +
+ + + + Whether to show "AM" and "PM", default is 'yes' if 'hrtype' is 12, and cannot be set to 'yes' if 'hrtype' is 24. + + An image for the background of the clock widget. Should be an ObjectSystem path. + + A color, either named or numeric, for the background of the clock. If neither bgcolor nor background are specified, the clock is transparent. + + Acts as a boolean to declare if the type will be bold (larger and thicker). + + A color, either named or numeric, for the foreground text of the clock. + + A color, either named or numeric, for the foreground text's shadow, if 'shadowed' is enabled. + + Stores the field name and memory. + + The height of the clock, in pixels. + + Set to 12 for a 12-hour clock, or to 24 for military (24-hour) time. + + Acts as a boolean to control if the entire clock can be moved. + + Whether or not the seconds should be displayed. Default is 'yes'. + + Whether or not the clock's text has a shadow. Default is 'no'. + + The horizontal offset, in pixels, of the shadow. + + The vertical offset, in pixels, of the shadow. + + The size, in points, clock's text. + + The width of the clock, in pixels. + + The X location of the left edge of the widget. Default is 0. + + The Y location of the top edge of the widget. Default is 0. + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + +
+ + + + + +

This widget is used to instantiate a custom component that has already been defined using a widget/component-decl widget, typically inside a ".cmp" file. The instantiation can be either static or dynamic: a static component is rendered along with the component or application that it resides inside, whereas a dynamic component is loaded as needed from the client. Components may also allow multiple instantiation when dynamic, which is especially beneficial with components whose top-level widget happens to be a widget/childwindow.

+ +
+ + + +

A component can be either visual or non-visual, and can be placed at almost any point in the application.

+ +

At the time of writing, only connectors may be placed inside a widget/component. It is planned that it be possible to place widgets inside a widget/component which then appear inside specially designated containers within the component itself. However that capability is not available at present.

+ +
+ + + + If enabled (dynamic single-instantiation components only), when a component is instantiated a second time, the original component is automatically destroyed so there is at most one instance at a time in existence. Defaults to 'yes'. + + Can specify another form to be a child of (if different than the implied form in which this control is nested). + + Height, in pixels, of the component. If unset, this defaults to the height of the component's containing widget. + + Either "static" or "dynamic". A static component is rendered with the application, whereas a dynamic one is loaded as needed from the client. Defaults to 'static'. + + If enabled (dynamic components only), the component can be instantiated more than once on the client. Defaults to 'no'. + + The path, in the OSML, to the component's definition (.cmp) file. (e.g. /sys/cmp/smart_field.cmp, /sys/cmp/form_controls.cmp, /samples/button.cmp). + + Acts as a boolean to indicate if there are any components on a higher level. + + A string that represents the data type. + + Width, in pixels, of the component. If unset, this defaults to the width of the component's containing widget. + + X-coordinate of the upper left corner of the component. If unset, defaults to 0. + + Y-coordinate of the upper left corner of the component. If unset, defaults to 0. + + + + + + Destroys the component. If multiple instances exist, then all instances are destroyed. + + Instantiates the component. + + + + + + This event is triggered when a dynamic component completes loading from the server. + + + + + + + + Type of smart field (e.g. label, editbox, checkbox, datetime). + + (e.g. readonly). Must have field be set in order to be "readonly". + + This is something which can accessed as :this_widget:value (returns the value of text which can't be directly accessed?), but can not be set directly through this property -- see SetValue listed under the Action section. + + + + + + (e.g. 1) + + Title to put across the form control bar (Note: the form_controls.cmp allows the user to perform row operations on a given record set). + + + + + + This is something which can accessed as :this_widget:content (returns the value of text which can't be directly accessed?), but can not be set directly through this property -- see SetValue listed under the Action section. + + + + + + + + + + + +
+ + + + + +

The component-decl widget is used to define a new component which can be used and reused in other applications or components. Creating a component is very similar to creating an application - except that the top-level widget in a component is "widget/component-decl" instead of "widget/page".

+ +
+ + + +

A widget/component-decl widget must occur at the top level of a component (.cmp) file.

+ +

Other visual and nonvisual widgets may be placed inside a component-decl, in addition to parameters and declarations of Events and Actions that the component generates and handles.

+ +

To declare that a component generates an Event, place a "widget/component-decl-event" inside the component at the top level. No parameters are needed for that Event. To cause the component to generate the Event, trigger an Action with the same name on the component-decl from inside, and the event will be activated for the containing application or component.

+ +

Similarly, to declare that a component can receive an Action, place a "widget/component-decl-action" inside the component at the top level. Again, no parameters are needed. The containing application or component can then trigger the Action, which will cause an event to occur inside the component. The event occurs on the component-decl widget (top level of the component), and can be caught with a connector widget.

+ +

Components can take parameters just like applications can. See the "widget/parameter" widget for details on how to declare parameters on applications and components.

+ +

Several of the properties of a component-decl are used to make it easy to wrap another widget or component. The expose_actions_for, expose_events_for, and expose_properties_for properties cause the actions, events, and properties of a widget or component inside the component-decl to be exposed to the "outside world". The apply_hints_to property causes any presentation hints ("widget/hints") that are applied in the instantiation of a component-decl (inside the "widget/component") to be applied to the specified widget or component inside the component-decl.

+ +
+ + + + Specifies a widget inside the component-decl that will receive any presentation hints constraints ("widget/hints") that are applied to the instantiation of this component-decl. + + Specifies a widget inside the component-decl whose actions (methods) we want to expose on the external interface of this component-decl. + + Specifies a widget inside the component-decl whose events we want to expose on the external interface of this component-decl. + + Specifies a widget inside the component-decl whose client-side properties we want to expose on the external interface of this component-decl. + + Indicates if the component is seen (not = -1). + + + + + + + Sends an alert to the screen. + + Loads the widget. + + Allows events to happen. + + + + + + Occurs when the widget has finished launching. + + + + + + + + + +
+ + + + + +

Each widget can have events and actions associated with it. The events occur when certain things occur via the user interface, via timers, or even as a result of data being loaded from the server. Actions cause certain things to happen to or within a certain widget, for example causing an HTML layer to reload with a new page, or causing a scrollable area to scroll up or down.

+ +

The connector widget allows an event to be linked with an action without actually writing any JavaScript code to do so -- the connector object is created, and given an event to trigger it and an action to perform when it is triggered.

+ +

Events and actions can have parameters, which specify more information about the occurrence of an event, or which specify more information about how to perform the action. Such parameters from events can be linked into parameters for actions via the connector widget as well.

+ +

When supplying a parameter to a connector (other than "action", "event", and "target"), there are several ways that parameter can be specified. First the parameter can be a constant value, defined explicitly in the application. Second, the parameter can be an expression which is evaluated on the client (using the runclient() pseudo-function) and which can change based on the conditions under which the connector is triggered. In this latter case, the runclient() expression can contain parameters supplied by the event, or values supplied by other widgets.

+ +
+ + + +

The connector object should be a sub-object of the widget which will trigger the event. Any widget with events can contain connectors as subwidgets.More than one connector may be attached to another widget's event.

+ +

An example connector would link the click of a URL in a treeview with the loading of a new page in an HTML area. See the Example source code at the end of this document to see how this is done.

+ +
+ + + + The name of the action which will be activated on another widget on the page. + + The name of the event that this connector will act on. + + The name of the widget generating the event (defaults to the widget the connector is placed inside of). + + The name of another widget on the page whose action will be called by this connector (if unspecified, defaults to the widget the connector is placed inside of). + + + + + + + + + + + +
+ + + + + +

The datetime widget displays a calendar and clock. Input is done by typing into the bar or by selecting a date in display pane. When the form goes into query mode two panes will appear, one for the start and one for the end time. Note that the osml format for searching for dates is dd Mon yyyy, whereas the format dates are displayed in the widget is Mon dd, yyyy.

+ +
+ + + +

This widget can be placed within any visual widget.

+ +
+ + + + The background color of the bar and pane (named or numeric). + + Default "no". If set to "yes" indicates that the datetime widget should only allow select and display a date, not a date and time. It may be useful to set default_time when using this option. + + When the user sets the date without selecting a time value (or if date_only is set), use this time as the "default" time. This can be set to "00:00:00" or "23:59:59", for example, to specify a time at the start or end of a day. + + The foreground color of the bar and pane (named or numeric). + + Not used currently to my knowledge. + + Name of a form object that is a parent of the datetime widget (not required). + + Height, in pixels, of the bar. + + Used to set the initialdate by a string. + + Default "yes". Specifies that when the datetime widget is in a form that is in search (QBF) mode, the datetime should display both a "start" and an "end" calendar so the user can search for records/objects matching a range of dates. + + Used to set the initial date by a query. + + Width, in pixels, of the bar. + + X-coordinate of the upper left corner of the bar. + + Y-coordinate of the upper left corner of the bar. + + + + + + This event occurs when the user clicks the widget. No parameters are available from this event. + + This event occurs when the user has modified the data value of the widget (clicked or unclicked it). + + This event occurs when the datetime widget receives keyboard focus (if the user tabs on to it or clicks on it, for instance). + + This event occurs when the datetime widget loses keyboard focus (if the user tabs off of it, for instance). + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + + + + Generates an internal data object using the data specified or the current time. + + + + + + + + + +
+ + + + + +

A dropdown form element widget that allows one of several options to be selected in a visual manner. The options are filled in using one of the child widgets, or via an SQL query to a database defined below.

+ +
+ + + +

This widget can be placed within any visual widget (or within a form widget). It may only contain 'dropdownitem' child objects, although it may of course also contain connectors as needed.

+ +
+ + + + The background color for the widget. + + Fieldname (from the dataset) to bind this element to. + + Represents the widget form (groups many widgets into one widget). + + Height, in pixels, of the dropdown. + + The color of the highlighted option when the mouse goes over the item. + + The default widget that has focus when the dropdown is first activated. + + If this is set to objectsource then the dropdown acts an objectsource client. + + Number of widgets displayed at once in the dropdown box. + + Represents the widget object source (transfers data to and from server). + + Width of popup dropdown list. + + If set to yes, this indicates that when the dropdown's form is in search (QBF) mode, the dropdown will allow multiple items to be selected, so the user can search for records/objects that match one of several values. The values will be listed in the dropdown, separated by commas. + + The SQL used to retrieve the list of items for the dropdown. It should have between two and five columns, in this order: label, value, selected (0 or 1, whether the item is selected by default), grp (group name), hidden (0 or 1 to hide the item from the dropdown list but still allow it to be a valid value). + + Width, in pixels, of the dropdown. + + X-coordinate of the upper left corner of the dropdown, default is 0. + + Y-coordinate of the upper left corner of the dropdown, default is 0. + + + + + + + + The text label to appear in the dropdown listing. + + The actual data that is stored with this item. + + + + + + + + This event occurs when the user has modified the data value of the widget (clicked or unclicked it). + + This event occurs when the data is changed (occurs when key press or button changes things). + + This event occurs when the edit bar receives keyboard focus (if the user tabs on to it or clicks on it, for instance). + + This event occurs when the edit bar of the dropdown loses keyboard focus (if the user tabs off of it, for instance). + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + This event occurs when right click is used on the mouse. + + + + + + Deletes all the widgets in the dropdown menu. + + This causes the dropdown to display a different Group of items (use the Group parameter). It can also restrict what items are displayed based on a minimum or maximum value (use Min and Max parameters). + + This specifies a SQL (use "SQL" parameter) query to use to re-load the contents of the dropdown. It should have between two and five columns, in this order: label, value, selected (0 or 1, whether the item is selected by default), grp (group name), hidden (0 or 1 to hide the item from the dropdown list but still allow it to be a valid value). + + Changes the child property "value". + + + + + + + + + +
+ + + + + +

An editbox form element widget allows the display and data entry of a single line of text (or a numeric value). When the editbox is clicked (and thus receives keyboard focus), the user can type and erase data inside the widget. Data which does not fit will cause the text to scroll. When it receives keyboard focus, the editbox displays a flashing I-beam cursor. The cursor color uses the data focus color for the page (default is '#000080', or dark blue).

+ +

When an editbox is part of a form and the form goes into query mode the editbox has several additional features. First, you can use the * operator as a wildcard (e.g. *ber matches September, October, November, December). Second you can enter a range if the field is numeric (e.g. 1-100). Thirdly, a list of criteria can be specified, separated by commas.

+ +
+ + + +

The editbox can be placed within any visual widget (or within a form widget). Editboxes automatically attach themselves to the form which contains them (whether directly or indirectly). Editboxes cannot contain visual widgets.

+ +
+ + + + A background image for the contents of the editbox. + + A color, RGB or named, for the editbox contents. If neither bgcolor nor background is specified, the editbox is transparent. + + A color, RGB or named, editbox's value description. The value description is displayed to the right of the value in the editbox, in parentheses, and in a different color. + + Specifies wither the edit box can be written in, default is true meaning the edit box cannot be written in. + + The value description to display in the editbox when the editbox is empty. For instance, "optional" or "required" or "type search and press ENTER" might be commonly used empty_description settings. + + Name of the column in the datasource you want to reference. + + Height, in pixels, of the editbox. + + Number of characters to accept from the user. + + Set to 'yes' if the user cannot modify the value of the edit box (default 'no'). + + Either "raised" or "lowered", and determines the style of the border drawn around the editbox. Default is "lowered". + + A string to display in a tooltip when the user rests the mouse pointer over the editbox. By default, the editbox will display a tooltip of its content if the content is too long to fit in the visible part of the editbox. + + Width, in pixels, of the editbox. + + X-coordinate of the upper left corner of the editbox, default is 0. + + Y-coordinate of the upper left corner of the editbox, default is 0. + + + + + + + + This event occurs before the data changes and can stop the data change event. + + This event occurs before the key press event is fired and can stop the key press event. + + This event occurs when the user clicks the widget. No parameters are available from this event. + + This event occurs when the user has modified the data value of the widget (clicked or unclicked it). + + This event occurs when the data is changed (occurs when key press or button changes things). + + This event occurs when the user presses the escape key. + + This event occurs when the editbox receives keyboard focus (if the user tabs on to it or clicks on it, for instance). + + This event occurs when the user presses any key. + + This event occurs when the editbox loses keyboard focus (if the user tabs off of it, for instance). + + This event occurs when the user releases the mouse button on the widget. + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user presses the return key. + + This event occurs when the user presses the tab key. + + + + + + The Enable action allows an edit box to be written in, and takes no parameters x from being written in, and takes no parameters. + + The SetFocus action selects and gives control to an edit box. + + The SetValue action modifies the contents of an editbox, and takes a single parameter, 'Value' (string). + + This action modifies the "value description" of the editbox, and takes a single parameter, 'Description' (string). + + + + + + + + + +
+ + + + + +

The execmethod widget is a nonvisual widget which is used to call ObjectSystem methods on objects on the server.This widget may become deprecated in the future when more advanced OSML API widgets become available.

+ +

These widgets are used via the activation of their "ExecuteMethod" action.

+ +
+ + + +

The execmethod widget, since it is nonvisual, can be placed almost anywhere but is typically placed at the top-level (within an object of type "widget/page") for clarity's sake. It has no effect on its container.These widgets cannot contain visual widgets, and since they have no Events, normally contain no connector widgets either.

+ +
+ + + + The name of the method to invoke. + + The ObjectSystem pathname of the object on which the method is to be run. + + A string parameter to pass to the method being invoked. + + + + + + Action causes the widget to execute the method on the server. It can take three parameters, which default to those provided in this widget's properties: "Objname", the object path, "Method", the method to invoke, and "Parameter", the parameter to pass to the method being invoked. + + + + + + + + + +
+ + + + + +

The form widget is used as a high-level container for form elements. Essentially, a form widget represents a single record of data, or the attributes of a single object in the objectsystem (or of a single query result set object). Form widgets must be used in conjunction with an ObjectSource widget, which does the actual transferring of data to and from the server.

+ +

Forms have five different "modes"of operation, each of which can be specifically allowed or disallowed for the form.

+ +
    + +
  1. No Data - form inactive/disabled, no data viewed/edited.
  2. + +
  3. View - data being viewed readonly.
  4. + +
  5. Modify - existing data being modified.
  6. + +
  7. Query - query criteria being entered (used for query-by-form applications)
  8. + +
  9. New - new object being entered/created.
  10. + +
+ +

Occasionally, the user may perform an operation which inherently disregards that the form may contain unsaved data. When this occurs and there is newly created or modified data in the form, the application must ask the user whether the data in the form should be saved or discarded, or whether to simply not even perform the operation in question. Since DHTML does not inherently have a "three-way confirm" message box (with save, discard, and cancel buttons), Centrallix allows a form to specify a "three-way confirm" window. This should be a hidden (visible=no) "widget/htmlwindow" object which may contain any content, but should at least contain three buttons named "_3bConfirmSave", "_3bConfirmDiscard", and "_3bConfirmCancel" directly in the htmlwindow. During a confirm operation, this window will become "application-modal"; that is, no other widgets in the application may be accessed by the user until one of the three buttons is pushed.

+ +

Several settings on the form widget control what state, or "mode", the form can be in: allow_query, allow_new, allow_modify, allow_view, and allow_nodata. These can beused to constrain a form to perform a specific task, such as only searching, or only creating new records. For example, a form with only allow_search enabled will always return to the search (QBF) mode and will never display the searched-for data that is returned in the objectsource.

+ +
+ + + +

Although the form widget itself is nonvisual in nature, it can contain visual widgets, including other containers, which may then in turn contain form elements as well. The form widget may be contained within any widget with a visual container (otherwise, the form elements would not show up on the page).

+ +
+ + + + Allow deletion of the displayed record. + + Allow modification of existing records. + + Allow creation of new records. + + Allow the 'no data' state. + + Default "no". If this is set to "yes", then the form will permit its data to be obscured (hidden from the user, via a window closure or tab page switch) and unsaved at the same time. If set to "no", the form will require the user to save or cancel any data modifications before the form may be obscured. + + Allow query by form. + + Allow viewing of existing data. + + Default "yes". Whether to automatically place the focus on the first form element in the form when the form transitions to the New, Modify, or Search mode for any reason other than the user manually putting focus on one of the form elements. + + Whether to pop up an OK/Cancel message box asking the user whether he/she is sure the record should be deleted. + + Set to true when the discard button of the 3 buttom confirm window (3bconfirmwindow) is pressed. + + Can be "save" (default), "nextfield", or "lastsave". Controls what to do when the user presses ENTER while in a form element in this form. "save" means to save the record immediately. "nextfield" means to move to the next form field, as if TAB were pressed instead. "lastsave" means to move to the next form field, but once ENTER is pressed on the very last field, to then save the record. Most people prefer "save" for this, since that is consistent with how normal applications work, but people in finance or accounting work often prefer "lastsave" since it allows them to perform high-speed data entry using a numeric keypad. + + Stores wither this form can be grouped with other forms. + + Enable MultiEnter. + + Specifies another form to transfer focus to when the user presses TAB at the end of the form. + + Similar to "next_form", but searches for the next form after this one within a given widget or component. Transitioning from one component context to another is permitted (you could specify a "widget/page" widget here, and cause focus to transfer to another form in the same application, regardless of level of component nesting; this may or may not be desired behavior). + + Represents the widget object source (transfers data to and from server). + + Allow changes. + + How to react to a tab in a control. + + When tabbing between form elements, if this is set to "yes", do not transfer focus to form elements that are not currently visible to the user. + + The name of the window to use for all 3-way confirm operations (save/discard/cancel). + + + + + + + + Fieldname (from the dataset) to bind this element to. + + + + + + + + + + + + This event occurs just before the form is enabled after a save. + + This event occurs when a child controller changes its data. + + This event occurs when the delete function is successfully completed. + + This event occurs when it is confirmed that the method search for object is available. + + This event occurs when the save action was successful. + + This event occurs after the discard action is performed. + + This event occurs at the end of the change mode action (always with status change event). + + This event occurs if and only if there was a mode change and the new mode is the 'Modify' state (always occurs with both a statusChange event and a ModeChange Event). + + This event occurs if and only if there was a mode change and the new mode is the 'New' state (always occurs with both a statusChange event and a ModeChange Event). + + This event occurs if and only if there was a mode change and the new mode is the 'NoData' state (always occurs with both a statusChange event and a ModeChange Event. + + This event occurs if and only if there was a mode change and the new mode is the 'Query' state (always occurs with both a statusChange event and a ModeChange Event. + + The even occurs when the form changes modes or when a child control changes its data. + + This event occurs if and only if there was a mode change and the new mode is the 'View' state (always occurs with both a statusChange event and a ModeChange Event. + + + + + + Clears the form to a 'no data'state. + + Deletes the current object displayed. + + Cancels an edit of form contents. + + Prevents interaction with the entire form. + + Allows editing of form's contents. + + Allows interaction with the form. + + Moves the form to the first object in the objectsource. + + Moves the form to the last object in the objectsource. + + Allows creation of new form contents. + + Moves the form to the next object in the objectsource. + + Moves the form to the previous object in the objectsource. + + Allows entering of query criteria. + + the query and returns data. + + Either executes a query or allows one to be made depending on the previous state. + + Saves the form's contents. + + Modifies a field and puts the form into a new modify or search mode if appropriate. + + Attemps to save data, if there is an error it returns false. + + Debugging tool to test the 3bcomfirm method (only use if you know what this method is). + + Changes the form to view mode. + + + + + + + Whether the form has a status or modifications that can be discarded / canceled. + + True if the form is in a state where the data can be edited (but isn't currently being edited). + + True if the form is in a state where a new record can be created. + + Whether the form can be placed into "QBF" (query by form) mode so that the user can enter query criteria. + + Whether the form is in "QBF" (query by form) mode so that the query can actually be run. + + Whether the form has data in it that can be saved with the Save action. + + The number of the last record in the query results, starting with '1'. Null if no data is available or if the last record has not yet been determined. + + The current record in the data source being viewed, starting with '1'. Null if no data is available. + + + + + + + + + +
+ + + + + +

Many times with multi-mode forms like those offered by Centrallix, the end-user can become confused as to what the form is currently "doing" (for instance, is a blank form with a blinking cursor in a "new" state or "enter query" state?). Centrallix helps to address this issue using the form status widget. A form status widget displays the current mode of operation that a form is in, as well as whether the form is busy processing a query, save, or delete operation. This clear presentation of the form's mode is intended to clear up any confusion created by a multi-mode form. This widget is a special-case form element.

+ +
+ + + +

The form status widget can only be used either directly or indirectly within a form widget. It can contain no visual widgets.

+ +
+ + + + The name of the form that this instance of form status corresponds to. + + Sets the style of the form widget. Can be "small", "large", or "largeflat". + + Allows you to set x position in pixels relative to the window. + + Allows you to set y position in pixels relative to the window. + + + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + + + + + + + + + +
+ + + + + +

The frameset widget provides the ability to construct a page consisting of multiple (potentially resizeable) frames.It is one of two possible top-level widgets (the page widget is the other one). Framesets can consist of one or more frames, arranged either in rows or columns.

+ +
+ + + +

The frameset can either be a top-level widget, or can be contained within a frameset (for subframes).The frameset widget should not be used anywhere else in an application. The frameset should contain only other framesets and/or pages.

+ +
+ + + + Number of pixels wide the border(s) between the frame(s) are. Can be set to zero. + + Whether the frames are arranged in rows or columns. Set this attribute to "rows" or "columns"respectively. + + Title of the frameset for an html page. + + + + + + + + Large this frame will be.Can either be expressed as an integer, which represents "50%". + + Width, in pixels, of the margins of the frame. + + + + + + + + + + + +
+ + + + + +

An autolayout widget with style set to "hbox". See "widget/autolayout".

+ +
+ +
+ + + + + +

The hints widget stores default values and other component modifying properties.

+ +
+ + + +

The hints widget can be placed inside of any visual component. Hints do not contain visual widgets.

+ +
+ + + + Defines a set of acceptable characters (e.g. allowchars="0123456789"). + + Defines a set of unacceptable characters. + + Defines a condition that the parent value must meet to be valid. + + A string or integer that specifies the initial, default, starting value for the component which containing this widget/hints widget. + + A list of string values that give all the possible values of th the parent. + + A sql query that is used to get the required value list. + + The way in which data is presented to the user. + + A numeric identification for a group of attributes. + + A string identification for a group of attributes. + + Determines rows of the widget. + + Length in characters that can be entered into this field. + + A string or integer that stores the maximum value of the attributes of its parent widget. + + A string or integer that stores the minimum value of the attributes of its parent widget. + + If the field is a bitmask, determines which ones are read only. + + Optional: contains a combination of 1 or more items following set {readonly, alwaysdef} separated by a comma if multiple items are chosen. + + Number of characters in the field shown to the user at once. + + + + + + + + + + + + +
+ + + + + +

The HTML area widget provides a way to insert a plain HTML document into a Centrallix generated page, either in-flow (static) or in its own separate layer that can be reloaded at will (dynamic). The HTML document can either be given in a property of the widget or can be referenced so that the HTML is read from an external document.

+ +

The HTML area widget also can act as a mini-browser -- clicking on hyper-text links in the visible document will by default cause the link to be followed, and the new document to be displayed in the HTML area (if the HTML area is dynamic).

+ +

Dynamic HTML areas do double-buffering and allow for transitions (fades) of various types.

+ +
+ + + +

The HTML area widget can be placed within any other widget that has a visible container (such as panes, pages, tab pages, etc.).

+ +
+ + + + Static contents for the HTML area. Usually used in lieu of "source" (see below). + + HTML source to be inserted at the end of the HTML document. + + height, in pixels, of the HTML area as a whole. + + Either "static" or "dynamic", and determines whether the HTML area is in-flow ("static") can be positioned and reloaded at-will. + + HTML source to be inserted at the beginning of the HTML document. + + The objectsystem path or URL containing the document to be loaded into the HTML as local server. + + Width, in pixels, of the HTML area as a whole. + + Dynamic HTML areas, the x coordinate on the container of its upper left corner. + + Dynamic HTML areas, the y coordinate on the container of its upper left corner. + + + + + + + + Loadpage action takes two parameters. "Source" contains the URL for the new page to be loaded into the HTML area.The optional parameter "Transition" indicates the type of fade to be used between one page and the next.Currently supported values are "pixelate", "rlwipe", and "lrwipe". + + + + + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + + + + +
+ + + + + +

The image widget displays a picture (image).

+ +

This widget can be a form element, in which case it will display an image as specified by the data from the form.

+ +
+ + + +

The image widget can be placed inside any container that allows for visual widgets. Only connectors may be placed inside it.

+ +
+ + + + Determines wither the image can be stretched or must stay its original aspect ration. + + If using a form for the image path, the name of the column in the datasource you want to reference. + + If using a form for the image path, this is the name of the form. If left unspecified, and fieldname is supplied, the default will be whatever form the image is inside. + + Height, in pixels, of the image. + + The words that come up if the image object cannot find its source and display an image. + + Width, in pixels, of the image. + + X-coordinate of the upper left corner of the image, relative to its container. + + Y-coordinate of the upper left corner of the image, relative to its container. + + + + + + This event occurs when the user clicks the checkbox. No parameters are available from this event. + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + Determines how much the image grows or shrinks compared to the original. + + An OSML pathname for the location of the image (such as a png or jpg file). + + Distance it is along the x axis from its parent widget. + + Distance it is along the y axis from its parent widget. + + + + + + Displays the image to user. + + Reloads the image with new offset values. + + Reloads the image with a new scale value. + + + +
+ + + + + +

The ImageButton widget provides a clickable button that is comprised of a set of two or three images.The first image is shown normally when the button is idle, the second when the button is pointed-to, and the third image is shown when the button is actually clicked. This provides a "tri-state" appearance much like that provided by buttons in modern user interfaces, although the button can be two-state, with just an "unclicked" and "clicked" version of the image.

+ +

The images are automatically swapped out when the appropriate mouse events occur on the button.

+ +

ImageButtons can also be disabled, and a "disabled" image is displayed at that time.

+ +

Unlike the textbutton, there is no 'tristate' property for an imagebutton; to make an imagebutton tri-state (different image when idle vs. when pointed to), use a 'pointimage', otherwise do not specify 'pointimage'.

+ +
+ + + +

The ImageButton can be placed inside any visible container, but only nonvisual widgets can be placed within it.

+ +
+ + + + The ObjectSystem pathname of the image to be shown when the user clicks the imagebutton. Defaults to 'image' if not specified. + + The ObjectSystem pathname of the image to be shown when the imagebutton is disabled. Defaults to 'image' if not specified. + + Height, in pixels, of the image button. + + The pathname of the image to be shown when the button is "idle". + + The pathname of the image to be shown when the button is pointed-to. Defaults to the 'image' if not specified. + + Whether to repeat the click event multiple times while the user holds down the button. + + The text that appears when the courser hovers over the image button. + + Width, in pixels, of the image button. + + X-coordinate of the upper left corner of the button, relative to its container. + + Y-coordinate of the upper left corner of the button, relative to its container. + + + + + + + + This event occurs when the user clicks the button. No parameters are available from this event. + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + This action causes the image button to enter its 'disabled' state, displaying the 'disabledimage' if specified. + + This action causes the image button to enter its 'enabled' state, possibly changing its appearance if a 'disabledimage' was explicitly specified. + + + + + + Whether or not the imagebutton should be enabled. Defaults to 'yes'. Can be an expression that if uses runclient() will cause the enabled status of the imagebutton to 'follow' the value of that expression while the application is running. + + + + + + + + + +
+ + + + + +

The label widget is used to display a non-editable string of text. It displays as a simple label.

+ +

A label can be used as a form element; to do this, specify a fieldname, and optionally a form.

+ +

A label can behave like a "link": to do this, specify a point_fgcolor and a click_fgcolor.

+ +
+ + + +

The label widget can be placed inside of any visual container, and will attach itself to any form widget that contains it (whether directly or indirectly). Labels may not contain visual widgets.

+ +
+ + + + Describes how the text should align in the label (e.g. right). + + It determines if the text can wrap around something. + + The color (named or #numeric) of the text in the label when the user clicks on the label. + + The color (named or #numeric) of the text in the label. + + The name of the column in the datasource that will be used to supply the text of the label. + + (e.g. 16, default: 12). + + The name of the form that this label is associated with. + + The height of the label. + + If text is no longer able to display shows an ellipsis. + + The color (named or #numeric) of the text in the label when the user hovers the mouse over the label. + + (e.g. bold). + + The text that the label is to display. + + The text displayed when the courser hovers over the widget. + + Describes how the text should align vertically within the label (e.g. top, middle, or bottom). + + The width of the label. + + X-coordinate of the upper left corner of the checkbox, default is 0. + + Y-coordinate of the upper left corner of the checkbox, default is 0. + + + + + + + + This event occurs when the user clicks the checkbox. No parameters are available from this event. + + This event occurs when the user has modified the data value of the checkbox (clicked or unclicked it). + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + Sets the value property to the given parameter. + + + + + + This property allows a runclient() expression to dynamically supply the text to display in the label. + + + + + + + + + +
+ + + + + +

The menu widget is used to create popup menus, drop-down menus, and menu bars. Menu widgets consist of a series of menuitem widgets which compose a menu.

+ +

Menu items are generally linked to actions on a page via the use of connectors. Simply place the connector inside the menu item widget.

+ +

Note: as of the time of writing, menu widgets were not yet properly functional.

+ +
+ + + +

Menus can be placed inside of any visual container. However, be aware that the menu will be clipped by its container, so placing them at the top-level can be of an advantage. Menu widgets contain menuitem widgets, which are also described in this section.

+ +
+ + + + A background image for a menu item that is selected (clicked on). + + A color, RGB or named, to be used as a selected (clicked) item's background. If neither active_bgcolor nor active_background are specified, the 'highlight' color or background is used instead. + + A background image for the menu. + + A color, RGB or named, to be used as the menu's background. If neither bgcolor nor background are specified, the menu is transparent. + + The width of the data elements in the menu. + + Either "horizontal" or "vertical" (default), and determines whether the menu is a drop-down/popup (vertical) or a menubar (horizontal). + + A color for the menu's text. + + Height, in pixels, of the menu, for menus with a direction of 'horizontal'. + + A background image for a menu item that is highlighted (pointed at). + + A color, RGB or named, to be used as a highlighted (pointed-at) item's background. If neither highlight_bgcolor nor highlight_background are specified, the standard color or background is used instead. + + Default "no". Popup menus disappear after an item on them is selected, whereas fixed menus remain visible (such as for menubars). + + Height, in pixels, of the menu items in a menu. + + Determines how far the shadow translates from the menu. + + Describes in a ratio how much larger or smaller the size of the shadow is compared to the window. + + Width, in pixels, of the menu. For menus with a direction of 'vertical', an unspecified width is determined dynamically based on the menu contents. + + X-coordinate of the upper left corner of the menu, relative to its container. + + Y-coordinate of the upper left corner of the menu, relative to its container. + + + + + + + + Optionally, a checkbox can be displayed to the left of the menu item when 'checked' is specified. In this case, the 'value' can also be a runclient() expression that controls whether the menu item is checked. + + Optionally, the pathname of an image file to display to the left of the menu item. + + The text to appear on the menu item. + + If set to "yes", then the menu item will be displayed on the righthand side of a horizontal menu bar (e.g., for having "File" "Edit" "Tools" on the left, and "Help" on the far right). + + The 'value' of the menu item, passed to the Selected event, below. If not specified, it defaults to the name of the widget (not its label). + + + + + + + + + + The text to appear on the menu title. + + + + + + + + This event occurs when the an item in the menu which is not a submenu is changed. + + This event occurs when the menu which previously was not clicked is clicked. + + This event occurs when the menu which previously was clicked is unselected. + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + This event fires when a menu item is selected. It can be placed in the menu itself, or inside the menu item widget. When on a menu item, it only fires when that item is selected. When on a menu, it passes the selected item's value as a parameter named 'Item' (string). + + + + + + This action causes a popup-type menu to become visible and appear at a selected (x,y) position on the page. When the user selects an item on the menu or clicks elsewhere on the page, the menu then disappears. Takes two parameters - X and Y, the (integer) positions on the page for the menu to appear. + + + + + + + + + +
+ + + + + +

The objectsource (osrc) widget lies at the core of Centrallix's ability to dynamically exchange data between the server and client. This widget implements a form of "replication" by maintaining a replica of a small segment of data in the user agent.

+ +

Both form and dynamic table widgets interact with the objectsource nonvisual widget to acquire data, update data, create data, and delete data. In fact, it is possible for more than one form and/or table to be connected with a given objectsource, to perform a variety of functions.

+ +

Objectsources offer synchronization with other objectsources via the Sync and DoubleSync actions (see below) or rule-based connnectivity (see widget/rule). These actions allow the application to contain multiple objectsources with primary key / foreign key relationships, and to have those objectsources automatically stay in synchronization with each other based on those relationships.

+ +

An objectsource may also be used to run a query which does not return any rows, such as an insert, update, or delete query. Under normal data maintenance conditions such queries are not needed as the objectsource handles those operations internally, however.

+ +
+ + + +

Objectsource widgets are often used at the top-level of an application, within a "widget/page" object, but can be used anywhere. The forms/tables that use an objectsource are generally placed within that objectsource. A page may have more than one objectsource widget.

+ +

Even though the objectsource is nonvisual, it is, like the form widget, a container, and can (should!) contain other visual and nonvisual widgets.

+ +
+ + + + One of oneachreveal, never, onload, onfirstreveal. (note: this autoquery setting is different from, but related to, the "widget/rule" "autoquery"). "onload" means that the osrc should run its query automatically when the .app containing this osrc is loaded by the user. "onfirstreveal" means that the osrc should run its query automatically when the data (e.g., a table or form) is first displayed to the user (e.g., the containing childwindow becomes visible or tabpage is selected). "oneachreveal" means to do so *each* time the data is displayed to the user. "never" means that the osrc should never query automatically by itself, but it may be triggered by a connector (QueryParam, QueryText, etc.) or by a widget/rule of type osrc_relationship. Important Note: If you expect to normally trigger the osrc via a relationship or via QueryParam, it is often *best* to set autoquery to 'never'. Otherwise, unexpected results can sometimes occur. + + If inserts and deletes are to function, the ObjectSystem pathname in which those inserts and deletes should occur. This should be one of the objects specified in the FROM clause of the SQL statement. + + Set of search parameters. + + Whether normal query activity on this objectsource indicates to the server that the user is active. Some objectsources (in conjunction with a widget/timer) regularly refresh their data; for those, indicates_activity can be set to "no" so that those refreshes don't cause a user's session to never time-out. + + The name of the object in the SQL query that contains the primary key for the query. + + Represents the number to fetch from the server when more records are needed from the server (such as when a form requests that the Current Record be a record beyond the end of the current replica contents). + + ** This feature currently disabled in Centrallix 0.9.1 ** Default "no". If set to "yes", the objectsource will ask the server to send it updates on any changes that occur on the server side (i.e., if the changes were made by another objectsource or by another user, they would be automatically refreshed into this objectsource in near real-time). + + The time between the data refreshing, if set to 0 it does not automatically refresh. + + Represents the number of records to store in its replica. This value should be larger than the maximum number of records that will be displayed at any one time. At times, Centrallix may increase the number of records cached on the client beyond this number. + + Acts as a boolean and delays query until the osrc is visable (if "true"). + + Similar to readahead, but relates to how many records should be fetched when more records are needed from the server to fulfill a request to display more data, such as a table widget scrolling without changing the current record. By default this is set to the value of 'readahead'. + + Default "yes". If set to "no", the objectsource will not relay any updates (modifications, etc.) that the user makes to the server. + + The SQL statement used to retrieve the data. + + Default "no". If set to "yes", then queries will be performed using a HAVING clause instead of a WHERE clause. + + + + + + + + When autoquery is set to true, when the master changes, the slave automatially requeries (otherwise have to explicitly call requery or refresh on the slave osrc). When autoquery is false, it causes relationships to be in enforced, but doesn't cause a re-query when the master's osrc refreshes / requeries. + + Defaults to 'yes'. If it is set to 'no', then this osrc is set up to be a master of the target, otherwise the default is for it to be a slave of the target. + + The field names in this objectsource to be used as the key value for the relationship. Keys can be key_1 through key_5. + + One of: allrecs, norecs, or sameasnull (the default). If the master osrc has no data loaded in it (no records), this determines how the slave (child) osrc behaves. 'allrecs' means to query for all records that match the sql query for the osrc, without additional constraints. 'norecs' means the slave (child) will be empty. 'sameasnull' means to handle it as if the key value in the master were NULL (see master_null_action). + + One of: allrecs, norecs, or nullisvalue (the default). If the master osrc has a NULL value for its key (as defined by key_#/target_key_#), this setting determines how the slave (child) osrc behaves. 'allrecs' and 'norecs' have the same meaning as for master_norecs_action. 'nullisvalue' means to treat the NULL as a value, and query for slave records that have a matching NULL values in their key (as defined by key_#/target_key_#). If no slave records have null values in the key, then 'nullisvalue' and 'norecs' have the same result. + + The target objectsource that this objectsource will be related to. + + The field names in the target objectsource to be used for the key value for the relationship (where # is an integer 1 through 5). These keys can be target_key_1 through target_key_5. + + + + + + The field in the specified 'osrc' in which to store the auto number. + + (e.g. counterosrc). + + The field name in this objectsource to be used as the key. + + The osrc in which to store the next auto id / counting number. + + + + + + + + This event occurs when a query is opened and the query ID is not null. + + This event occurs when data is created in the object source. + + This event is invoked whenever the current record changes to a different record. + + This event is invoked when a query is completed and the last row(s) retrieved. + + This event occurs when data is modified in the object source. + + This event occurs when replicant entries are swapped. + + + + + + + Creates a base of an object and notifies all childeren that a child is creating an object. + + If it is called while an object is being created it stops the creation and cleans up the mess. + + Changes the data item that the object source points to in the server. + + Clears the replica of data. + + Encapsulates data in an array and calls CreateObject. + + Creates an object though OSML + + Deletes an object through OSML. + + Stops relations with all clients and forces them to resync. + + DEPRECATED: Performs a double synchronization with two other objectsources, known as the Parent and the Child, in two steps. The first step is like Sync (see below), with a ParentOSRC and ParentKey1-ParentKey9] / ParentSelfKey1-ParentSelfKey9. Next, a Sync is performed between the current objectsource and the ChildOSRC in the same way the first step performed a sync between the ParentOSRC and the current objectsource, respectively, using SelfChildKey1-SelfChildKey9 / ChildKey1-ChildKey9. + + Searches for a certain object in the replica (retrieved records), and makes it the current object. Parameters: To search by record number, set ID equal to the integer (1 = first record). To search by object name (primary key), set Name equal to a string containing the primary key (note that concatenated keys use | as a separator). To search by other abitrary field values, set those values in the parameters to this action. + + Returns first record in the replica. + + Returns last record in the replica. + + Modifies an object through OSML. + + Returns next record in the replica. + + Order Object. + + Returns previous record in the replica. + + Query. + + Query Object. Primarily for internal use only. + + Refreshes the query, and allows new parameter values to be passed in. Previous comment: Re-runs the SQL query, but adds more constraints. The additional constraints are provided as parameters to the connector which invokes this action. + + Runs the query, searching for objects whose attributes *contain* a combination of string values. 'query' contains a space-separated list of strings that must be present in each returned record (typically the 'query' is typed by the user). 'field_list' is a comma-separated list of field names to search in. Each field name (attribute) can be preceded by a * or followed by a *; the presence of these asterisks controls whether the matching is done on the entire attribute value or just as a substring match. Examples: 'my_key,*my_description*' for field_list means to match exact values for my_key, and match anywhere in my_description. cx__case_insensitive can be set to 1 to make the search case insensitive. + + Re-runs the SQL query using the previously stored parameter values (if any). This is used when the data on the server is believed to have changed, and the new data is desired to be displayed in the application. This action does not change the current row. + + Re-runs the SQL query for just the one current object (record). This does not change the currently selected record in the osrc. This should be used when the intent is to refresh the values of just the current object without affecting anything else. + + DEPRECATED: Performs a synchronization operation with another objectsource by re-running the query for this objectsource based on another objectsource's data. Used for implementing relationships between objectsources. The ParentOSRC (string) parameter specifies the name of the objectsource to sync with. Up to nine synchronization keys can be specified, as ParentKey1 and ChildKey1 through ParentKey9 and ChildKey9. The ParentKey indicates the name of the field in the ParentOSRC to sync with (probably a primary key), and ChildKey indicates the name of the field (the foreign key) in the current objectsource to match with the parent objectsource. + + Save the clients that connect and lets clients of clients know that orsc is conected to them. + + Moves the current row backwards. + + Moves the current row forwards. + + + + + + + + + +
+ + + + + +

The page widget represents the HTML application (or subapplication) as a whole and serves as a top-level container for other widgets in the application. The page widget also implements some important functionality regarding the management of keypresses, focus, widget resizing, and event management. When creating an application, the top-level object in the application must either be "widget/page" or "widget/frameset", where the latter is used to create a multi-framed application containing multiple page widgets.

+ +

Page widgets specify the colors for mouse, keyboard, and data focus for the application. Focus is usually indicated via the drawing of a rectangle around a widget or data item, and for a 3D-effect two colors are specified for each type of focus: a color for the top and left of the rectangle, and another color for the right and bottom of the rectangle. Mouse focus gives feedback to the user as to which widget they are pointing at (and thus which one will receive keyboard and/or data focus if the user clicks the mouse). Keyboard focus tells the user which widget will receive data entered via the keyboard. Data focus tells the user which record or data item is selected in a widget.

+ +
+ + + +

The page widget cannot be embedded within other widgets on a page. There must only be one per page, unless a frameset is used, in which case page widgets may be added within a frameset widget.

+ +
+ + + + Determines how close the window is to the browser edge. + + A background image for the page. + + The background color for the page. Can either be a recognized color (such as "red"), or an RGB color (such as "#C0C0C0"). + + A color, RGB or named, for the top and left edges of rectangles drawn to indicate data focus (default is dark blue). + + A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate data focus (default is dark blue). + + The default font to use in the application (such as "Arial"). + + The default font size, in pixels (same as points on a typical 72dpi display), for text in the application. + + The height of the page in pixels. + + When the application is rendered over HTTP/HTML, this controls the X-Frame-Options anti-clickjacking HTTP response header. Possible values are "none", "sameorigin", and "deny". The default value is set by the x_frame_options setting in the "net_http" section of centrallix.conf (see configuration docs for more information). The http_frame_options setting only applies to the page widget it is set in, not to any components that the page loads (it can be set in those components separately if needed). + + The file path to the page icon. + + A color, RGB or named, to be used for the top and left edges of rectangles drawn to indicate keyboard focus (default is "white"). + + A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate keyboard focus (default is dark grey). + + Color for hyper links. + + True if page is loaded, false otherwise. + + The maximum amount of server requests at one time. + + A color, RGB or named, to be used for the top and left edges of rectangles drawn to indicate mouse focus (default is "black"). + + A color, RGB or named, for the bottom and right edges of rectangles drawn to indicate mouse focus (default is "black"). + + Set this to "yes" to display message boxes when application errors occur, such as an undefined property reference. The default is "no", but this is handy when debugging. + + The title which will appear in the browser's window title bar. + + The default color for text which appears on the page. Can either be a named color or RGB (numeric) color. + + One or more comma-separated pathnames for widget templates to use. The specified files should contain "widget/template" objects (see "widget/template for details). + + The width of the page in pixels. + + + + + + + + + + This occurs when the page initializes. + + This occurs when the uses right clicks on a mouse or mouse pad. + + + + + + Sends an alert widget. Set the 'Message' to specify a text string that should appear in the alert. + + Closes the page. + + Starts a new app in a new window. + + Logs data to the console (using console.log()), for testing and debugging. Set the 'Message' to specify a text string that should appear in the log. + + Loads the page. + + Reloads the page in the user's browser. Note: This event forces a reload, even if the original content could be loaded without one. + + + + + + + + + +
+ + + + + +

The pane is Centrallix's simplest container. It consists only of a background and a border, which can either have a "raised" edge or "lowered" edge style.

+ +
+ + + +

This container can be placed inside any widget having a visible container. Visual or nonvisual widgets may be placed inside a pane.

+ +
+ + + + A background image for the pane. + + A color, RGB or named, to be used as the pane's background. If neither bgcolor nor background is specified, the pane will be transparent. + + The radius (sharpness) of the pane, smaller is more sharp. + + For "bordered" pane styles, this is the color of the border, either named or a #number. + + Height, in pixels, of the pane. + + The color of the shadow. + + The placement of the shadow with respect to the window. + + A radius that describes the sharpness of the corners of the shadow (smaller means sharper). + + "raised", "lowered", "bordered", or "flat". Determines the style of the pane's border. + + Width, in pixels, of the pane. + + X-coordinate of the upper left corner of the pane, relative to its container. + + Y-coordinate of the upper left corner of the pane, relative to its container. + + + + + + + + Indicates if the user can interact with the pane (if so "true"). + + + + + + This event occurs when the user clicks the checkbox. No parameters are available from this event. + + This event occurs when the user presses the mouse button on the checkbox. This differs from the 'Click' event in that the user must actually press and release the mouse button on the checkbox for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the checkbox. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the checkbox. + + This event occurs when the user first moves the mouse pointer over the checkbox. It will not occur again until the user moves the mouse off of the checkbox and then back over it again. + + This event occurs when the user releases the mouse button on the checkbox. + + + + + + Creates a triangular pointer on the edge of a pane to point at a given (X,Y) coordinate. + + Changes the width and height of the pane to the given (Width,Height). + + Sets the backgound image or color of the pane, using the attribute Color or Image. + + + + + + + + + +
+ + + + + +

Applications (.app) and components (.cmp) can accept parameters, which can then be used to control the way that the application or component is rendered or the way it functions. The widget/parameter widget is used to declare a parameter on an application or component, and to specify data type, default values, and more. ObjectSource widgets can also have parameters, which are used when running the ObjectSource's SQL query.

+ +
+ + + +

Parameters must be placed inside a widget/page, widget/component-decl, or widget/osrc. They cannot be used anywhere else, and cannot contain other widgets.

+ +

Parameters have a very similar set of properties to the widget/hints widget, which is used to attach "presentation hints" to a widget.

+ +

To use a parameter value, just reference it in a runserver() expression with object "this", as in :this:parametername. For "object" parameters, reference them by just using the parameter name as if you would use the name of the object itself if the object were physically present in the component instead of being passed as a parameter (see sample code).

+ +
+ + + + Parameter widgets are treated as other widgets and normally would appear as widgets in the namespace on the client, with the parameter values being available in runclient() expressions on the client. For efficiency reasons, however, parameters to static components, other than parameters of type "object", are not deployed to the client. To override this behavior, set this option to "yes". + + If this is a parameter to a component, and the parameter has type "object", this can be set to a type of widget that should be searched for in the containing application or component once the component is instantiated. Note that the object being searched for must be a container of the component instance, either directly or indirectly. This option is frequently used to link in with a form or objectsource in the contianing application or component, without that form or objectsource having to be explicitly passed to the component. + + Gives the name of the parameter being passed, thus allowing the parameter widget name (which must be unique within the application or component) to be different from the parameter name as referenced within expressions, SQL, or connectors. + + The data type of the parameter. Can be "integer", "string", "double", "datetime", "money", or "object". + + + + + + This sets the value of the parameter. + + + + + + This event occurs when the parameter being passed is changed. + + + + + + + + + +
+ + + + + +

A radio button panel widget is a form element used to create a set of radio buttons on screen. Only one radio button may be selected at a time. When the form goes into query mode the radio buttons change into checkboxes and more than one can be selected at a time.

+ +
+ + + +

The radio button panel can be placed inside of any visual container, and will automatically attach itself to a form widget if it is inside of one (directly or indirectly). The "widget/radiobuttonpanel" is the main widget, and can contain any number of "widget/radiobutton" widgets which specify the choices which will be present on the panel. No other visual widgets can be contained within a radio button panel.

+ +

Note: form widget interaction was not yet implemented as of the time of writing of this document.

+ +
+ + + + Name of the column in the datasource you want to reference. + + A background image for the radio button panel. + + A color, RGB or named, for the panel background. If neither bgcolor nor background transparent. + + Height, in pixels, of the panel. + + An image to be used for the rectangular border drawn around the radio buttons. + + The color, RGB or named, of the text within the panel. Default: "black". + + The title for the radio button panel, which appears superimposed on the rectangular border around the radio buttons. + + Width, in pixels, of the panel. + + X-coordinate of the upper left corner of the panel, default is 0. + + Y-coordinate of the upper left corner of the panel, default is 0. + + + + + + + + The text label to appear beside the radio button. + + the radio button is initially selected or not. Should only be set on one radio Default;"false". + + The value of the selected item. + + + + + + + + + + + +
+ + + + + +

The remote control nonvisual widget allows for one application (or instance of an application) to activate Actions in another running application, even if those applications are on two separate client computer systems. This is done by passing the event/action information through a remote control channel on the server.

+ +

Two remote control widgets are required: a master and slave. This widget is the slave widget, which receives remote control events via the Centrallix server. When a master widget (remotemgr) sends an event through the channel, this slave widget is automatically activated and can then trigger the appropriate action on another widget on the page.

+ +

In order for the remote control event to be passed through Centrallix, the master and slave widgets must both be using the same channel id and be logged in with the same username.They need not be a part of the same session on the server.

+ +

Note: at the time of this writing, the remotectl widget was not yet operational.

+ +
+ + + +

The remote control widget is a nonvisual widget and thus cannot contain visual widgets. It is normally located at the top-level of an application, within a "widget/page" object.

+ +
+ + + + + + + +
+ + + + + +

The remote control manager nonvisual widget allows for one application (or instance of an application) to activate Actions in another running application, even if those applications are on two separate client computer systems. This is done by passing the event/action information through a remote control channel on the server.

+ +

Two remote control widgets are required: a master and slave. This widget is the master widget, which sends remote control events via the Centrallix server. When a this widget sends an event through the channel, the slave widget (remotectl) is automatically activated and can then trigger the appropriate action on another widget on the remote application's page.

+ +

In order for the remote control event to be passed through Centrallix, the master and slave widgets must both be using the same channel id and be logged in with the same username.They need not be a part of the same session on the server.

+ +

Note: at the time of this writing, the remotemgr widget was not yet written.

+ +
+ + + +

The remote control manager widget is a nonvisual widget and thus cannot contain visual widgets.It is normally located at the top-level of an application, within a "widget/page" object.

+ +
+ + + + + + + +
+ + + + + +

The 'repeat' nonvisual widget is used to repeat its entire subtree of widgets for each record in an sql query.

+ +
+ + + +

This widget has no content of its own, so it is only useful if it has widgets inside it. For positioning of visual widgets inside a widget/repeat, an hbox or vbox (outside the widget/repeat) can be used, or the x and y can be set mathematically based on results from the SQL query.

+ +

The widget/repeat can be useful in creating data-driven user interfaces, as well as in facilitating a plug-in architecture in your application. For instance, the SQL query could retrieve a list of matching components to be included in an interface, and the repeat widget could create components, tabs, windows, table columns, buttons, etc., for each returned SQL query record.

+ +
+ + + + The sql query that will be run on the server. + + + + + + + + + +
+ + + + + +

The rule widget is used as an important part of Centrallix's declarative application development. It specifies behavior expected from a widget or relationship between two widgets. For instance, one type of rule, the osrc_relationship rule, ties two objectsources together to enforce a primary key / foreign key relationship between the two.

+ +
+ + + +

Various widgets can have rule widgets; the various types of rule widgets are described in the sections for the widgets that they relate to.

+ +
+ + + + The type of rule. For instance, a "widget/osrc" can have "osrc_relationship" and "osrc_key" rules. + + + +
+ + + + + +

The scrollbar is used to allow the uesr to control a numeric value; typically the scrollbar is tied to the scrolling behavior of another widget.

+ +

Currently, both table and scrollpane widgets have their own scrollbars, so this widget is not used for either of those.

+ +
+ + + +

As a visual widget, the scrollbar can be placed anywhere a visual widget is permitted.

+ +
+ + + + A background image to be placed behind the scrollbar. + + A color, RGB or named, to be used as the scrollbar background. If neither bgcolor nor background is supplied, the scrollbar is transparent. + + Either "horizontal" or "vertical" indicating the visible direction that the scrollbar is oriented. + + Height, in pixels, of the scrollbar. + + The upper limit of the range of the scrollbar's value. The value will range from 0 to the number specified by 'range'. This property can be set to a runclient() dynamic expression, in which case the range will change as the expression changes. + + This acts as a boolean to denote if the scroll bar is visible ("true" is visible). + + Width, in pixels, of the scrollbar. + + X-coordinate of the upper left corner of the scrollbar, relative to its container. + + Y-coordinate of the upper left corner of the scrollbar, relative to its container. + + + + + + Sets the scroll bar to a specific location determined by the parameter. + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + +
+ + + + + +

The scrollpane widget provides a container and a scrollbar. The scrollbar can be used to move up and down in the container, so more content can be placed in the container than can be normally viewed at one time.

+ +

The scrollbar includes a draggable thumb as well as up and down arrows at the top and bottom. Clicking the arrows scrolls the content of the container up or down by a small amount, whereas clicking on the scrollbar itself above or below the thumb will scroll the area by a large amount.

+ +
+ + + +

Scrollpane widgets can be placed inside any other container, but are usually placed inside a pane or a tab page. Almost any content can be placed inside a scrollpane, but most commonly tables, treeviews, and html areas appear there.

+ +
+ + + + A background image to be placed behind the scrollpane. + + A color, RGB or named, to be used as the scrollpane background.If neither bgcolor transparent. + + height, in pixels, of the scrollpane's visible area. Due to the nature of the can time. + + allows user to set scroll pane to visible (true) or not (false). + + width, in pixels, of the scrollpane, including the scrollbar area on the right side. + + x-coordinate of the upper left corner of the scrollpane, relative to its container. + + y-coordinate of the upper left corner of the scrollpane, relative to its container. + + + + + + Scrolls to a specific location determined by the scroll bar. + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + + + + + + + + +
+ + + + + +

The TabControl widget provides a DHTML tab control within Centrallix. The widget behaves in the same way as tab controls in other GUI environments, providing a set of tab pages, layered one on top of the other, which can be selected (brought to the foreground) by clicking the mouse on the respective visible tab at the top of the tab control.

+ +

To further distinguish which tab at the top of the tab control is active, this widget slightly modifies the X/Y position of the tab as well as changing a thumbnail image (on the left edge of the tab) to further enhance the distinction between selected and inactive tab pages.

+ +
+ + + +

The tab pages are containers, and as such, controls of various kinds, including other tab controls, can be placed inside the tab pages.

+ +

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", with the exception of nonvisuals such as connectors.

+ +

Tab pages also have a 'visible' property which allows them to be hidden and revealed. This is used if the type is set to dynamic, but can be used manually as well.

+ +
+ + + + An image to be used as the background of the tab control. + + As an alternate to "background", "bgcolor" can specify a color, either named or RGB. + + A color that outlines window. + + A radius that describes the sharpness of the corners of the window (smaller means sharper). + + Determines the look of the outline. + + The height, in pixels, of the tab control, including the page height but not including the height of the tabs at the top. + + An image to be used as the background of the tabs which are inactive (in the background). + + As an alternate to "inactive_background", "inactive_bgcolor" can specify a color, either named or RGB. + + The placement of the shadow described as a rotational transformation with respect to the window. + + The color of the shadow. + + The placement of the shadow with respect to the window. + + A radius that describes the sharpness of the corners of the shadow (smaller means sharper). + + The location of the tabs: "top" (default), "bottom", "left", "right", or "none". + + The width of the tabs in pixels. This is optional for tab_locations of "top", "bottom", and "none". + + The color of the text to be used on the tabs to identify them. + + allows user to set tab to visible (true) or not (false). + + Width, in pixels, of the tab control, including the page width but not the width of the tabs (if they are at the side). + + X-coordinate of the upper left corner of the tab control, relative to the container. + + Y-coordinate of the upper left corner of the control, relative to its container. + + + + + + + + This is the fieldname from the objectsource that the tabpage will use for its title. + + The name of the tab page, which appears in the tab at the top of the control. + + If this is set to dynamic then the tabpage will act as an objectsource client, displaying zero or more tabs: one tab for each item in the replica with title set to the value of the fieldname. In this way, a tab control can have a mix of dynamic tabs and static ones. + + 0 or 1. Can contain a dynamic runclient() expression to control when the tab page is visible to the user. + + + + + + + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + This event occurs when the visible tab changes. + + + + + + The name of the tab page that should be initially selected. This can also contain a dynamic runclient() expression controlling which tab page is selected. + + Similar to "selected", but selects the tab by numeric index. + + + + + + Sets the selected tab according to the parameter given (the tab itself or its index). + + + + + + + + + +
+ + + + + +

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data.The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

+ + Table widgets come in three different flavors: static, dynamicpage, and dynamicrow.Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget.Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will.Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query.As of the time of writing of this document, only static mode and dynamicrow mode were supported. + +

Table widgets allow the selection (keyboard, mouse, and data focus) of individual rows.

+ +
+ + + +

Table widgets are normally placed inside of a scrollpane so that any rows which don't fit int the container can still be viewed. Table columns are created via "widget/table-column" child widgets within the table widget.

+ +
+ + + + Whether to permit the user to select rows in the table. Default "yes". + + A background image for the table. This "shows through" between table cells. + + A color, RGB or named, to be used between table cells and rows.If neither bgcolor nor background are specified, the table is transparent. + + The horizontal spacing between cells in the table, in pixels. Default is 1. + + The vertical spacing between cells in the table, in pixels. Default is 1. + + The width of the column separation lines in pixels. Default is 1. + + Either "rows" (default) or "properties". In "properties" mode, the table displays one row per attribute, and so only displays the current record in the objectsource. In "rows" mode, the table displays one row per record in the objectsource. + + Acts as a boolean to only show the scrollbar when it is needed (activates when it is set to "1"). + + Whether to allow dragging of column boundaries to resize columns; set to 1 to allow it and 0 to disallow. Default is 1. + + Set to 'yes' to cause the table's current row to follow the currently selected object in the ObjectSource, and 'no' to disable this behavior. Default is 'yes'. + + Whether to show the table's grid in rows which do not hold data. Set to 1 to show the grid or 0 to not show it; default is 1. + + A background image for the header row cells. + + A color, RGB or named, for the header row cells. + + Acts as a boolean to determine if the scrollbar can be seen (hide is "1"). + + The height in pixels of the table. + + The maximum height of the image from the child. + + The maximum width of the image from the child. + + Sets which data element in the table has focus initially. + + width of the inner spacing between cells in a table. Default0. + + margins within each cell, in pixels. Default is 0 pixels. + + The maximum height of each row. + + The minimum height of each row. + + A background image for the "new row" placeholder that is visible when a new object is being created. + + A color, RGB or named, for the "new row" placeholder that is visible when a new object is being created. + + Identifies the OSRC that the table connects to. + + width of the outer spacing around the outside of the table, in pixels. Default0. + + Acts as a boolean to allow the scroll bar to overlab with the table (allow is "1"). + + Acts as a boolean to reverse to order of the table elements (reverses if set to "1"). + + A background image for the table row cells. + + A color, RGB or named, for the table row cells. + + A background image for the table row cells. If this is specified, rows will alternate in backgrounds between "row_background1" and "row_background2". + + A color, RGB or named, for the table row cells. If this is specified, rows will alternate in colors between "row_bgcolor1" and "row_bgcolor2". + + Determines the color of the edge of the rows. + + Determines the sharpness of the row corners, smaller is more sharp. + + How many rows are shown in the table. + + The height of the individual rows in pixels. Default is 15 pixels. + + A background image for the current (selected) row cells. + + A color, RGB or named, for the current (selected) row cells. + + The color of the shadow of the row. + + How far the shadow is from the row. + + The sharpness of the corners of the shadow, smaller means more sharp. + + Whether to highlight the currently selected row. Default "yes". + + A color, RGB or named, of the text in the normal data rows. + + A color, RGB or named, of the text in the "new row" placeholder. + + A color, RGB or named, of the text in the highlighted data row. + + Whether to show the title bar of the table. Default "yes". + + A color, RGB or named, of the text in the header row. If unset, defaults to textcolor. + + Width, in pixels, of the table.The height is determined dynamically. + + The maximum number of rows to show at any given time, for dynamic tables. + + X-coordinate of the upper left corner of the table. Default is 0. + + Y-coordinate of the upper left corner of the table. Default is 0. + + + + + + + + The alignment of the column: "left" or "right". + + It is a pseudonym for the fieldname. + + The color of the caption_fieldname. + + Acts as a boolean to determine if items are group able. + + The height in pixels of the column. + + The title of the column to be displayed in the header row. + + The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. + + width of the column. + + Determines if text can wrap around an obstacle. + + + + + + + + The Click event fires when a user clicks on a row. + + The DblClick event fires when the user double-clicks the mouse on a row. Passes three values, 'Caller' which is the table's name, 'recnum' which is the sequential number of the record in the table, and 'data' which is an array of the data in the selected record. + + The Click event fires when a user right clicks on a mouse or mouse pad. + + + + + + Drops all elements from the table. + + + + + + + + + +
+ + + + + + + + + +

A widget/template must be a root widget of a file (which normally is given a .tpl extension).

+ +

Each child in a widget/template is a "rule". Each rule applies to widgets that both 1) have the same 'widget_class' property value (there can be only one widget_class per widget (and "rule")) and 2) match the widget type of the child (eg "widget/imagebutton").

+ +

Every other property of the "rule" are default values.

+ +

All children of the "rule" are automatically inserted into the matched widgets.

+ +
+ + + + + + + + The value of this property must match another widget before said widget will "inherit" this "rule". + + + + + + + + + + 1); + + cnFirst "widget/connector" { event="Click"; target=template_form; action="First"; } + + } + + } + + ]]> + + + +
+ + + + + +

The textarea is a multi-line text edit widget, allowing the user to enter text data containing line endings, tabs, and more. It also contains a scrollbar which allows more text to be edited than can fit in the displayable area.

+ +
+ + + +

The textarea is a visual form element widget, and can be contained inside any container capable of holding visual widgets. It may only contain nonvisual widgets like the connector.

+ +
+ + + + A background image for the textarea. + + A color, RGB or named, to be used as the background. If neither bgcolor nor background are specified, the textarea is transparent. + + The field in the objectsource to associate this textarea with. + + Links text area to a form. + + The height of the textarea, in pixels + + The maximum number of characters the textarea should accept from the user + + Can hold text, html, or wiki and display. + + Set to 'yes' if the data in the text area should be viewed only and not be modified. + + The border style of the text area, can be 'raised' or 'lowered'. Default is 'raised'. + + The width of the textarea, in pixels + + The horizontal coordinate of the left edge of the textarea, relative to the container. + + The vertical coordinate of the top edge of the textarea, relative to the container. + + + + + + + + This event occurs before the key press event is fired and can stop the key press event. + + This event occurs when the user has modified the data value of the widget (clicked or unclicked it). + + This event occurs when the data is changed (occurs when key press or button changes things). + + This event occurs when the user presses the escape key. + + This event occurs when the editbox receives keyboard focus (if the user tabs on to it or clicks on it, for instance). + + This event occurs when the user presses any key. + + This event occurs when the editbox loses keyboard focus (if the user tabs off of it, for instance). + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + This event occurs when the user presses the tab key. + + + + + + Checks to see if things need to be changed (new is different from old) then calls set value. + + Sets the focus on a selected widget. + + Sets the content to the text parameter sent. + + + + + + + + + + + +
+ + + + + +

A textbutton provides similar functionality to the imagebutton. However, the programmer need not create two or three graphics images in order to use a textbutton; rather simply specifying the text to appear on the button is sufficient.

+ +

Textbuttons, like imagebuttons, can either have two or three states. A three-state textbutton doesn't have a "raised" border until the user points to it, whereas a two-state textbutton retains its raised border whether pointed to or not.

+ +
+ + + +

The TextButton can be placed inside any visible container, but only nonvisual widgets can be placed within it.

+ +
+ + + + Sets the alignment of text in the button, can have left right or center (default). + + A background image for the button. + + A color, RGB or named, to be used as the button's background.If neither bgcolor nor background are specified, the button is transparent. + + A color that outlines the button. + + A radius that describes the sharpness of the corners of the button (smaller means sharper). + + Determines the look of the outline. + + A color, RGB or named, to be used for the button's text when it is disabled. + + Whether the button is enabled (can be clicked). Default is 'yes'. Also supports dynamic runclient() expressions allowing the enabled status of the button to follow the value of an expression. + + A color, RGB or named, for the text on the button. Default "white". + + A color, RGB or named, for the text's 1-pixel drop-shadow. Default "black". + + Height, in pixels, of the text button. + + File path to the source of the image. + + Defines the height of image. + + Defines spacing between image and the border. + + Defines the width of image. + + Describes where an image is in the text button (top-default, right, bottom, left). + + The text to appear on the button. This may be a dynamic runclient() expression to dynamically change the button's text. + + Whether or not the button is tri-state (does not display a raised border until the user points at it). Default is yes. + + The width, in pixels, of the text button. + + X-coordinate of the upper left corner of the button, relative to its container. + + Y-coordinate of the upper left corner of the button, relative to its container. + + + + + + + + + + This event occurs when the user clicks the button. No parameters are available from this event. + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + + + + + Called from a connector, this action sets the button's text to the value passed in through connector's "Text" parameter. + + + + + + + + + +
+ + + + + +

A timer widget is used to schedule an Event to occur after a specified amount of time. Timers can be set to expire a set amount of time after the page is loaded, or they can be triggered into counting down via activating an Action on the timer. Timers can be used to create animations, delayed effects, and more.

+ +
+ + + +

Timers are nonvisual widgets which can be placed almost anywhere in an application. They are most commonly found at the top-level of the application, however. Timers have no direct effects on the object in which they are placed. Timers can only contain Connector widgets.

+ +
+ + + + The timer starts counting down again immediately after it expires. + + The timer starts counting down immediately after the page loads. + + Of milliseconds (1/1000th of a second) before the timer expires. + + + + + + Actions causes a timer to stop counting down, and thus no Expire event will occur until another countdown sequence is initiated by a SetTimer action. + + Action takes two parameters: "Time" (integer in milliseconds) and "AutoReset" (integer 0 or 1). It causes a timer to begin counting down towards an Expire event. + + + + + + This occurs when the timer hits its timeout and the auto reset is not enabled. + + + + + + + + + +
+ + + + + +

A treeview provides a way of viewing hierarchically-organized data via a "traditional" GUI point-and-click tree structure. A treeview has "branches" that can expand and collapse entire subtrees of data.

+ +

Centrallix treeviews present a subtree of the ObjectSystem, with the data dynamically loaded, on demand, from the server as the widget is used. The treeview widget thus has no intelligence in and of itself in determining what kinds of objects are presented at each level of the tree. Many times, this is exactly what is desired because the treeview is being used to simply browse objects in the ObjectSystem, such as directories and files. In other cases, the treeview is teamed up with a special ObjectSystem object called a "querytree" (QYT) object. The querytree object creates a hierarchical view from other potentially non-hierarchical data in the ObjectSystem, such as that from different database tables and so forth.

+ +
+ + + +

Treeviews can be placed inside of any visual container, but are usually placed inside of a scrollpane, since scrollpanes can expand to allow the user to view data that would not normally fit inside the desired container. Treeviews can contain only nonvisual widgets such as connectors.

+ +
+ + + + A color, RGB or named, to be used for the text for the items in the treeview. + + A background image for the selected item in the treeview. + + A color, RGB or named, to be used for the text for the selected item. + + A pathname to an image to be used as icons for the items in the treeview. + + Orders treeview in descending (desc) and ascending (anything else) order. + + Whether to display the connections between items in the tree using lines. + + Whether to display the root of the tree. If "no", then the root is auto-expanded and only its children are visible. + + Whether to display the connection lines between the root and the root's immediate children. If "no", the root's children are flush against the lefthand side of the treeview, otherwise the connecting lines show up. + + The ObjectSystem path of the root of the treeview. + + If set to "yes", the branch lines are drawn in a 3D style, otherwise they are drawn in a single color. + + Width, in pixels, of the treeview. + + X-coordinate of the upper left corner of the treeview's root object. + + Y-coordinate of the upper left corner of the treeview's root object. + + + + + + This event occurs when the user clicks while the treeview is in focus. + + Occurs when the user clicks on the clickable link for an item in the treeview. Its one parameter is "Pathname", or the ObjectSystem path to the object which was selected. + + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the mouse pointer off of the widget. + + This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. + + This event occurs when the user releases the mouse button on the widget. + + This event occurs when one of the elements of the treeview is given focus. + + To the above, but when the user right-clicks on an item in the treeview. + + + + + + Searches for a specific element. + + Returns the next element in the treeview. + + Transfers focus to the selected element. + + Sets the selected element as the root. + + + + + + + + + +
+ + + + + +

The 'variable' nonvisual widget is used to create a global javascript scalar variable in the application.The variable can be an integer or string.

+ +
+ + + +

This nonvisual widget is used at the top-level (within a "widget/page"). Currently it has no events, and so shouldn't contain any visual or nonvisual widgets.

+ +
+ + + + The field with which to link / connect to the variable. + + The form (e.g. it's osrc) with which to associate the variable and fieldname (if different than the form in which variable is currently nested). + + Can be accessed as :var_name:value. + + + + + + Called by a widget/connector: Sets the value of the variable to whatever is specified by the "Value" parameter. + + + + + + This event occurs when the user has modified the data value of the widget (clicked or unclicked it). + + This event occurs when the data is changed (occurs when key press or button changes things). + + + + + + + + + +
+ + + + + +

An autolayout widget with style set to "vbox". See "widget/autolayout".

+ +
+ +
+ + + +
diff --git a/centrallix-os/sys/js/ht_utils_wgtr.js b/centrallix-os/sys/js/ht_utils_wgtr.js index 1379bacf9..085992ee4 100644 --- a/centrallix-os/sys/js/ht_utils_wgtr.js +++ b/centrallix-os/sys/js/ht_utils_wgtr.js @@ -363,7 +363,7 @@ function wgtrIsUndefined(prop) // wgtrGetServerProperty() - return a server-supplied property value function wgtrGetServerProperty(node, prop_name, def) { - var val = node.__WgtrParams[prop_name]; + var val = node?.__WgtrParams?.[prop_name]; if (typeof val == 'undefined') return def; else if (typeof val == 'object' && val && val.exp) diff --git a/centrallix-os/sys/js/htdrv_page.js b/centrallix-os/sys/js/htdrv_page.js index 1da3ba1e5..85c58c5b5 100755 --- a/centrallix-os/sys/js/htdrv_page.js +++ b/centrallix-os/sys/js/htdrv_page.js @@ -1354,6 +1354,8 @@ function pg_init(l,a,gs,ct) //SETH: ?? ia.Add("Launch", pg_launch); ia.Add("Close", pg_close); ia.Add("Alert", pg_alert); + ia.Add("Log", pg_log); + ia.Add("ReloadPage", pg_reload_page); // Events var ie = window.ifcProbeAdd(ifEvent); @@ -1435,6 +1437,16 @@ function pg_alert(aparam) alert(aparam.Message); } +function pg_log({ Message }) + { + console.log(Message); + } + +function pg_reload_page() + { + window.location.reload(); + } + function pg_reveal_cb(e) { if (e.eventName == 'ObscureOK') From 162d23585df7c25b8f96257dbcb776c1e6eeeec3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:23:47 -0700 Subject: [PATCH 026/101] Add a warning when using the pane resize action, which breaks responsiveness. --- centrallix-os/sys/js/htdrv_pane.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_pane.js b/centrallix-os/sys/js/htdrv_pane.js index 9489912e7..598e51feb 100644 --- a/centrallix-os/sys/js/htdrv_pane.js +++ b/centrallix-os/sys/js/htdrv_pane.js @@ -79,12 +79,9 @@ function pn_setbackground(aparam) function pn_action_resize(aparam) { - // I think this code is a better implementation of the following lines: - // var w = (aparam.Width) ?? pg_get_style(this, 'width'); - // var h = (aparam.Height) ?? pg_get_style(this, 'height'); - - var w = aparam.Width?aparam.Width:pg_get_style(this, 'width'); - var h = aparam.Height?aparam.Height:pg_get_style(this, 'height'); + console.warn('Resize action called which will probably break responsiveness.', this, aparam); + const w = (aparam.Width) ?? pg_get_style(this, 'width'); + const h = (aparam.Height) ?? pg_get_style(this, 'height'); resizeTo(this, w, h); } From 5d65ee7f8acb6b114c9a88769e428d7c8b36b8d1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:28:36 -0700 Subject: [PATCH 027/101] Make the point action responsive. Add htr_action_point() to ht_render.js to handle the point action responsively. Refactor pane and window widgets to use the htr_action_point(). Improve documentation and code clarity for htutil_point(). Improve documentation for the point action. --- centrallix-doc/Widgets/widgets.xml | 2 +- centrallix-os/sys/js/ht_render.js | 35 +++++++++++++++++++++++++ centrallix-os/sys/js/ht_utils_layers.js | 15 ++++++++--- centrallix-os/sys/js/htdrv_pane.js | 4 +-- centrallix-os/sys/js/htdrv_window.js | 4 +-- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index 42b81a8f6..315452e93 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -629,7 +629,7 @@ checkbox_test "widget/page" Opens the window. If the parameter IsModal is set to 1, then the window becomes modal (only the window's contents are accessible to the user until the window is closed). If the parameter NoClose is set to 1, then the close button in the upper right corner of the window becomes inactive and the window will only close via the Close, SetVisibility, and ToggleVisibility actions. - Makes the window relocate to a side using a triangle (pop over). + Creates a triangular pointer on the edge of the window to point at a given (X,Y) coordinate. Opens a window like a pop-up. diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index 6b80b9ace..8d0163e10 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -819,6 +819,41 @@ function htr_stylize_element(element, widget, prefix, defaults) ); } +/*** When a point action is invoked the first time, a ResizeObserver is also + *** created to update the location of the point element if the document is + *** resized. The parameters from invocation of this function are saved and + *** reused to update the point each time a resize occurs. + */ +function htr_action_point(aparam) + { + const updatePoint = () => + { + /** Get the parameters from the resize observer object. **/ + const { X, Y, AtWidget, BorderColor, FillColor } = this.resizeObserver.aparam; + const { p1, p2 } = htutil_point(this, X, Y, AtWidget, BorderColor, FillColor, this.point1, this.point2); + this.point1 = p1; + this.point2 = p2; + } + + if (!this.resizeObserver) + { + /*** There isn't a resize observer yet, so create a new one that will + *** update the point when the document is resized. + *** Note: ResizeObserver provides a list of resized entries to the + *** callback (updatePoint()) and we ignore it because we only care + *** that something has been resized, htutil_point() handles the rest. + ***/ + const resizeObserver = this.resizeObserver = new ResizeObserver(updatePoint); + resizeObserver.observe(document.documentElement); + } + /** I really wanted to write the code below to show off how cleaver I am, but the if statement above is far more readable. **/ + // (!this.resizeObserver) && (this.resizeObserver = new ResizeObserver(updatePoint)).observe(document.documentElement); + + /** Save the parameters, then update the point. **/ + this.resizeObserver.aparam = aparam; + updatePoint(); + } + function htr_alert(obj,maxlevels) { alert(htr_obj_to_text(obj,0,maxlevels)); diff --git a/centrallix-os/sys/js/ht_utils_layers.js b/centrallix-os/sys/js/ht_utils_layers.js index 096754eb5..600db733f 100644 --- a/centrallix-os/sys/js/ht_utils_layers.js +++ b/centrallix-os/sys/js/ht_utils_layers.js @@ -20,6 +20,12 @@ function htutil_tag_images(d,t,l,ml) } } +/*** + *** @param bc BorderColor + *** @param fc FillColor + *** @param p1 Point 1 + *** @param p2 Point 2 + ***/ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) { // Determine x/y to point at @@ -105,6 +111,7 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) } // Set the CSS to enable the point divs + const { top: wtop, left: wleft } = $(wthis).offset(); $(p1).css ({ "position": "absolute", @@ -114,8 +121,8 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) "border-style": "solid", "box-sizing": "border-box", "content": "", - "top": (top + $(wthis).offset().top) + "px", - "left": (left + $(wthis).offset().left) + "px", + "top": (top + wtop) + "px", + "left": (left + wleft) + "px", "border-color": c1, "visibility": "inherit", "z-index": htr_getzindex(wthis) + 1 @@ -129,8 +136,8 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) "border-style": "solid", "box-sizing": "border-box", "content": "", - "top": (top + doffs.y + $(wthis).offset().top) + "px", - "left": (left + doffs.x + $(wthis).offset().left) + "px", + "top": (top + doffs.y + wtop) + "px", + "left": (left + doffs.x + wleft) + "px", "border-color": c2, "visibility": "inherit", "z-index": htr_getzindex(wthis) + 2 diff --git a/centrallix-os/sys/js/htdrv_pane.js b/centrallix-os/sys/js/htdrv_pane.js index 598e51feb..854cee0e0 100644 --- a/centrallix-os/sys/js/htdrv_pane.js +++ b/centrallix-os/sys/js/htdrv_pane.js @@ -87,9 +87,7 @@ function pn_action_resize(aparam) function pn_action_point(aparam) { - var divs = htutil_point(this, aparam.X, aparam.Y, aparam.AtWidget, aparam.BorderColor, aparam.FillColor, this.point1, this.point2); - this.point1 = divs.p1; - this.point2 = divs.p2; + htr_action_point.call(this, aparam); } function pn_init(param) diff --git a/centrallix-os/sys/js/htdrv_window.js b/centrallix-os/sys/js/htdrv_window.js index b551430f4..6cdcf0da9 100644 --- a/centrallix-os/sys/js/htdrv_window.js +++ b/centrallix-os/sys/js/htdrv_window.js @@ -152,9 +152,7 @@ function wn_init(param) function wn_action_point(aparam) { - var divs = htutil_point(this, aparam.X, aparam.Y, aparam.AtWidget, aparam.BorderColor, aparam.FillColor, this.point1, this.point2); - this.point1 = divs.p1; - this.point2 = divs.p2; + htr_action_point.call(this, aparam); } // Popup - pops up a window in the way that a menu might pop up. From 2f070dff3ba87b9fe86bafba12d6d135ca56e82a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:31:33 -0700 Subject: [PATCH 028/101] Make the autolayout widget responsive. Update al_reflow() to create responsive CSS using the setResponsive() functions. Improve documentation for al_childresize(). Optimize and clean up inefficient code. --- centrallix-os/sys/js/htdrv_autolayout.js | 61 ++++++++++++++---------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_autolayout.js b/centrallix-os/sys/js/htdrv_autolayout.js index fabace1ca..427f42849 100644 --- a/centrallix-os/sys/js/htdrv_autolayout.js +++ b/centrallix-os/sys/js/htdrv_autolayout.js @@ -9,6 +9,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. +/*** This function works entirely using server-side dimensions. + *** Responsive dimensions are handled elsewhere. + *** + *** @param child The child to be resized. + *** @param oldw The previous width in server-side adaptive coordinates. + *** @param oldh The previous height in server-side adaptive coordinates. + *** @param neww The new width in server-side adaptive coordinates. + *** @param newh The new height in server-side adaptive coordinates. + ***/ function al_childresize(child, oldw, oldh, neww, newh) { if (oldw != neww || oldh != newh) @@ -37,29 +46,27 @@ function al_reflow_buildlist(node, children) } } +/*** Note: + *** This function gets all its values from server properties, so its generated + *** positions are in server layout. Thus, we convert them right before they + *** assigned to allow for responsive values. + ***/ function al_reflow() { // Get configuration var width = wgtrGetServerProperty(this,"width"); var height = wgtrGetServerProperty(this,"height"); - var spacing = wgtrGetServerProperty(this,"spacing"); - if (!spacing) spacing = 0; - var cellsize = wgtrGetServerProperty(this,"cellsize"); - if (!cellsize) cellsize = -1; - var align = wgtrGetServerProperty(this,"align"); - if (!align) align = "left"; - var justify_mode = wgtrGetServerProperty(this,"justify"); - if (!justify_mode) justify_mode = "none"; + var spacing = wgtrGetServerProperty(this,"spacing",0); + // var cellsize = wgtrGetServerProperty(this,"cellsize",-1); // Unused + var align = wgtrGetServerProperty(this,"align","left"); + // var justify_mode = wgtrGetServerProperty(this,"justify","none"); // Unused var type = "vbox"; if (wgtrGetServerProperty(this,"style") == "hbox" || wgtrGetType(this) == "widget/hbox") type = "hbox"; - var column_width; - if (type == "vbox") - column_width = wgtrGetServerProperty(this,"column_width"); + var column_width, row_height; + if (type == "vbox") column_width = wgtrGetServerProperty(this,"column_width"); + else row_height = wgtrGetServerProperty(this,"row_height"); if (!column_width) column_width = width; - var row_height; - if (type == "hbox") - row_height = wgtrGetServerProperty(this,"row_height"); if (!row_height) row_height = height; // Build the child list @@ -87,10 +94,9 @@ function al_reflow() for(var i=0; i width) { if (xo > 0 && row_height > 0 && row_offset + row_height*2 + spacing <= height) @@ -108,6 +114,7 @@ function al_reflow() } else if (type == 'vbox') { + const cheight = wgtrGetServerProperty(child, "height"); if (yo + cheight > height) { if (yo > 0 && column_width > 0 && column_offset + column_width*2 + spacing <= width) @@ -148,10 +155,9 @@ function al_reflow() for(var i=0; i width) { if (xo > 0 && row_height > 0 && row_offset + row_height*2 + spacing <= height) @@ -164,17 +170,19 @@ function al_reflow() } if (child.tagName) { - setRelativeX(child, xo + xalign); - if (wgtrGetServerProperty(child,"r_y") == -1) - setRelativeY(child, row_offset); + setResponsiveX(child, xo + xalign); + const r_y = wgtrGetServerProperty(child, "r_y"); + if (r_y == -1) + setResponsiveY(child, row_offset); else - setRelativeY(child, row_offset + wgtrGetServerProperty(child,"r_y")); + setResponsiveY(child, row_offset + r_y); } xo += cwidth; xo += spacing; } else if (type == 'vbox') { + const cheight = wgtrGetServerProperty(child, "height"); if (yo + cheight > height) { if (yo > 0 && column_width > 0 && column_offset + column_width*2 + spacing <= width) @@ -187,11 +195,12 @@ function al_reflow() } if (child.tagName) { - setRelativeY(child, yo + yalign); - if (wgtrGetServerProperty(child,"r_x") == -1) - setRelativeX(child, column_offset); + setResponsiveY(child, yo + yalign); + const r_x = wgtrGetServerProperty(child, "r_x"); + if (r_x == -1) + setResponsiveX(child, column_offset); else - setRelativeX(child, column_offset + wgtrGetServerProperty(child,"r_x")); + setResponsiveX(child, column_offset + r_x); } yo += cheight; yo += spacing; From 9559ffc030fe76e719573211458bcdadf36ba7f1 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Fri, 6 Feb 2026 17:39:10 -0700 Subject: [PATCH 029/101] Make the tab widget responsive. Rewrite C & JS code to use responsive styling. Fix a bug that allowed multiple tabs to be selected at once. Fix spelling errors. Clean up. --- centrallix-os/sys/js/htdrv_tab.js | 549 +++++++++++----------- centrallix/htmlgen/htdrv_tab.c | 753 +++++++++++++++++++++--------- 2 files changed, 809 insertions(+), 493 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_tab.js b/centrallix-os/sys/js/htdrv_tab.js index ebab8b6b6..96fd166e8 100644 --- a/centrallix-os/sys/js/htdrv_tab.js +++ b/centrallix-os/sys/js/htdrv_tab.js @@ -9,7 +9,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. - // Sets the value of the current tab (but not the appearance), without // triggering on-change events. function tc_set_tab_unwatched() @@ -27,139 +26,150 @@ function tc_set_tab_unwatched() // Makes the given tab current. function tc_makecurrent() { - var t; - if (this.tabctl.tloc != 4 && htr_getzindex(this.tab) > htr_getzindex(this.tabctl)) return 0; - for(var i=0;i htr_getzindex(t)) return 0; + for(let i = 0; i < tabs.length; i++) { - t = this.tabctl.tabs[i]; - if (t != this && (t.tabctl.tloc == 4 || htr_getzindex(t.tab) > htr_getzindex(this.tab))) + const cur = tabs[i]; + if (cur !== this && (cur.tabctl.tloc === 'None' || htr_getzindex(cur.tab) > htr_getzindex(tab))) { - htr_setzindex(t, htr_getzindex(this.tabctl) - 1); - htr_setvisibility(t, 'hidden'); - if (t.tabctl.tloc != 4) + htr_setzindex(cur, htr_getzindex(t) - 1); + htr_setvisibility(cur, 'hidden'); + if (cur.tabctl.tloc !== 'None') { - htr_setzindex(t.tab, htr_getzindex(this.tabctl) - 1); - t.tab.marker_image.src = '/sys/images/tab_lft3.gif'; - moveBy(t.tab, this.tabctl.xo, this.tabctl.yo); - //setClipItem(t.tab, t.tabctl.cl, getClipItem(t.tab, t.tabctl.cl) + t.tabctl.ci); - if (this.tabctl.inactive_bgColor) htr_setbgcolor(t.tab, this.tabctl.inactive_bgColor); - if (this.tabctl.inactive_bgnd) htr_setbgimage(t.tab, this.tabctl.inactive_bgnd); + htr_setzindex(cur.tab, htr_getzindex(t) - 1); + cur.tab.marker_image.src = '/sys/images/tab_lft3.gif'; + cur.tab.classList.remove('tab_selected'); + // moveBy(cur.tab, t.xo, t.yo); + if (t.inactive_bgColor) htr_setbgcolor(cur.tab, t.inactive_bgColor); + if (t.inactive_bgnd) htr_setbgimage(cur.tab, t.inactive_bgnd); } } } - htr_setzindex(this, htr_getzindex(this.tabctl) + 1); + htr_setzindex(this, htr_getzindex(t) + 1); htr_setvisibility(this,'inherit'); - if (this.tabctl.tloc != 4) + if (tloc !== 'None') { - if (this.tabctl.main_bgColor) htr_setbgcolor(this.tab, this.tabctl.main_bgColor); - if (this.tabctl.main_bgnd) htr_setbgimage(this.tab, this.tabctl.main_bgnd); - htr_setzindex(this.tab, htr_getzindex(this.tabctl) + 1); - this.tab.marker_image.src = '/sys/images/tab_lft2.gif'; - moveBy(this.tab, -this.tabctl.xo, -this.tabctl.yo); - //setClipItem(this.tab, this.tabctl.cl, getClipItem(this.tab, this.tabctl.cl) - this.tabctl.ci); + if (t.main_bgColor) htr_setbgcolor(tab, t.main_bgColor); + if (t.main_bgnd) htr_setbgimage(tab, t.main_bgnd); + htr_setzindex(tab, htr_getzindex(t) + 1); + tab.marker_image.src = '/sys/images/tab_lft2.gif'; + tab.classList.add('tab_selected'); + // moveBy(tab, -t.xo, -t.yo); } this.setTabUnwatched(); - this.tabctl.ifcProbe(ifEvent).Activate("TabChanged", {Selected:this.tabctl.selected, SelectedIndex:this.tabctl.selected_index}); + t.ifcProbe(ifEvent).Activate("TabChanged", { Selected:t.selected, SelectedIndex:t.selected_index }); } -function tc_makenotcurrent(t) +function tc_makenotcurrent(page) { - htr_setzindex(t,htr_getzindex(t.tabctl) - 1); - htr_setvisibility(t,'hidden'); - if (t.tabctl.tloc != 4) + const { tabctl, tab } = page; + + htr_setzindex(page, htr_getzindex(tabctl) - 1); + htr_setvisibility(page, 'hidden'); + + if (tabctl.tloc !== 'None') { - htr_setzindex(t.tab,htr_getzindex(t.tabctl) - 1); - t.tab.marker_image.src = '/sys/images/tab_lft3.gif'; - moveBy(t.tab, t.tabctl.xo, t.tabctl.yo); - //setClipItem(t.tab, t.tabctl.cl, getClipItem(t.tab, t.tabctl.cl) + t.tabctl.ci); - if (t.tabctl.inactive_bgColor) htr_setbgcolor(t.tab, t.tabctl.inactive_bgColor); - if (t.tabctl.inactive_bgnd) htr_setbgimage(t.tab, t.tabctl.inactive_bgnd); + htr_setzindex(page.tab,htr_getzindex(page.tabctl) - 1); + tab.marker_image.src = '/sys/images/tab_lft3.gif'; + tab.classList.remove('tab_selected'); + // moveBy(tab, tabctl.xo, tabctl.yo); + if (tabctl.inactive_bgColor) htr_setbgcolor(tab, tabctl.inactive_bgColor); + if (tabctl.inactive_bgnd) htr_setbgimage(tab, tabctl.inactive_bgnd); } } - -// Adds a new tab to the tab control -function tc_addtab(l_tab, l_page, l, nm, type,fieldname) + +/*** Adds a new tab to the tab control. This function deals with whether or + *** not that tab is selected as LITTLE AS POSSIBLE since that piece of state + *** should be handled elsewhere with functions like tc_makenotcurrent() or + *** tc_makecurrent(). + *** + *** @param l_tab The tab being added. + *** @param l_page The page to which the tab is being added. + *** @param l The layer where this change will occur. + *** @param nm The name of the tab. + ***/ +function tc_addtab(l_tab, l_page, l, nm, type, fieldname) { - var newx; - var newy; - if (!l_tab) l_tab = new Object(); + const tabctl = this, { tabs } = tabctl, { tloc, tab_h, tab_spacing } = l; + + let x, y; + if (!l_tab) l_tab = {}; l_page.tabname = nm; l_page.type = type; l_page.fieldname = fieldname; - l_page.tabindex = this.tabs.length+1; + l_page.tabindex = tabs.length + 1; htr_init_layer(l_page,l,'tc_pn'); ifc_init_widget(l_page); - if (l.tloc != 4) + + /** Calculate the location and flexibility to render the tab. **/ + if (tloc === 'None') + { + x = 0; + y = 0; + } + else { - htr_init_layer(l_tab,l,'tc'); - if (l.tloc == 0 || l.tloc == 1) // top or bottom + htr_init_layer(l_tab, l, 'tc'); + + /** Calculate x coordinate. **/ + if (tloc === 'Top' || tloc === 'Bottom') { - if (this.tabs.length > 0) + if (tabs.length > 0) { - //alert(htr_getphyswidth(this.tabs[this.tabs.length-1])); - newx = getPageX(this.tabs[this.tabs.length-1].tab) + $(this.tabs[this.tabs.length-1].tab).outerWidth() + 1; - if (htr_getvisibility(this.tabs[this.tabs.length-1]) == 'inherit') newx += l.xo; + const previous_tab = tabs[tabs.length - 1].tab; + x = getRelativeX(previous_tab) + $(previous_tab).outerWidth() + tab_spacing; } + else if (l.tab_fl_x) + /** Copy tabctl.style.left to avoid small but noticeable inconsistencies. **/ + setRelativeX(l_tab, tabctl.style.left); else - newx = getPageX(this); + /** Math for inflexible tabs do not suffer from inconsistencies. * */ + x = getRelativeX(tabctl); } - else if (l.tloc == 2) // left - newx = getPageX(this)- htr_getviswidth(l_tab) + 0; - else if (l.tloc == 3) // right - newx = getPageX(this) + htr_getviswidth(this) + 1; + else if (tloc === 'Left') + x = getRelativeX(tabctl) - htr_getviswidth(l_tab); + else if (tloc === 'Right') + x = getRelativeX(tabctl); // + htr_getviswidth(tabctl) // Included in xtoffset (see below) - if (l.tloc == 2 || l.tloc == 3) // left or right + /** Calculate y coordinate. **/ + if (tloc === 'Left' || tloc === 'Right') { - if (this.tabs.length > 0) + if (tabs.length > 0) { - newy = getPageY(this.tabs[this.tabs.length-1].tab) + 26; - if (htr_getvisibility(this.tabs[this.tabs.length-1]) == 'inherit') newy += l.yo; + const previous_tab = tabs[tabs.length - 1].tab; + y = getRelativeY(previous_tab) + tab_h + tab_spacing; } + else if (l.tab_fl_y) + /** Copy tabctl.style.top to avoid small but noticeable inconsistencies. **/ + setRelativeY(l_tab, tabctl.style.top); else - newy = getPageY(this); + /** Math for inflexible tabs do not suffer from inconsistencies. * */ + y = getRelativeY(tabctl); } - else if (l.tloc == 1) // bottom - newy = getPageY(this)+ htr_getvisheight(this) + 1; - else // top - newy = getPageY(this) - 24; - - // Clipping - switch(l.tloc) + else if (tloc === 'Bottom') + y = getRelativeY(tabctl); // + htr_getvisheight(tabctl) // Included in ytoffset (see below) + else // Top + y = getRelativeY(tabctl) - tab_h; + + /** Apply the same tab offsets used on the server. **/ + x += l.xtoffset; + y += l.ytoffset; + + /** Space out tab away from previous tab to account for borders. **/ + if (tabs.length > 0) { - case 0: // top - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()+10) + 'px, 25px, -10px)'); - break; - case 1: // bottom - $(l_tab).css('clip', 'rect(0px, ' + ($(l_tab).outerWidth()+10) + 'px, 35px, -10px)'); - break; - case 2: // left - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()) + 'px, 35px, -10px)'); - break; - case 3: // right - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()+10) + 'px, 35px, 0px)'); - break; + switch (tloc) + { + case 'Top': case 'Bottom': x += 2; break; + case 'Left': case 'Right': y += 2; break; + } } } - else - { - newx = 0; - newy = 0; - } - if (htr_getvisibility(l_page) != 'inherit') - { - if (l.tloc != 4) - { - newx += l.xo; - newy += l.yo; - //setClipItem(l_tab, l.cl, getClipItem(l_tab, l.cl) + l.ci); - if (l.inactive_bgColor) htr_setbgcolor(l_tab, l.inactive_bgColor); - else if (l.main_bgColor) htr_setbgcolor(l_tab, l.main_bgColor); - if (l.inactive_bgnd) htr_setbgimage(l_tab, l.inactive_bgnd); - else if (l.main_bgnd) htr_setbgimage(l_tab, l.main_bgnd); - } - } - else + if (htr_getvisibility(l_page) === 'inherit') { htr_unwatch(l,"selected","tc_selection_changed"); htr_unwatch(l,"selected_index","tc_selection_changed"); @@ -167,49 +177,60 @@ function tc_addtab(l_tab, l_page, l, nm, type,fieldname) l.selected_index = l_page.tabindex; l.current_tab = l_page; l.init_tab = l_page; - pg_addsched_fn(window,"pg_reveal_event",new Array(l_page,l_page,'Reveal'), 0); + pg_addsched_fn(window,"pg_reveal_event",[l_page,l_page,'Reveal'], 0); htr_watch(l,"selected", "tc_selection_changed"); htr_watch(l,"selected_index", "tc_selection_changed"); - if (l.tloc != 4) + if (tloc !== 'None') { if (l.main_bgColor) htr_setbgcolor(l_tab, l.main_bgColor); if (l.main_bgnd) htr_setbgimage(l_tab, l.main_bgnd); } } - if (l.tloc != 4) + + if (tloc !== 'None') { - var images = pg_images(l_tab); - for(var i=0;i this.tabs.length) return o; + if (tabindex < 1 || tabindex > tabs.length) return o; // okay to change tab. - //this.tabs[tabindex-1].makeCurrent(); + //tabs[tabindex-1].makeCurrent(); if (this.selchange_schedid) pg_delsched(this.selchange_schedid); - this.selchange_schedid = pg_addsched_fn(this,"ChangeSelection1", new Array(this.tabs[tabindex-1]), 0); + this.selchange_schedid = pg_addsched_fn(this, "ChangeSelection1", new Array(tabs[tabindex - 1]), 0); return n; } -function tc_action_set_tab(aparam) +function tc_action_set_tab({ Tab, TabIndex }) { - if (aparam.Tab) this.selected = aparam.Tab; - else if (aparam.TabIndex) this.selected_index = parseInt(aparam.TabIndex); + if (Tab) this.selected = Tab; + else if (TabIndex) this.selected_index = parseInt(TabIndex); } function tc_showcontainer() @@ -263,13 +286,13 @@ function tc_showcontainer() function tc_clear_tabs(tabs) { - for(var i=0;i "+vals[j]+" \n"; else - content = "\n
 "+vals[j]+" 
\n"; - tabparent = tc_direct_parent(tabs[i]); + content = + '\n\t' + + '' + + '' + + '
 ' + vals[j] + ' 
\n'; + + tabparent = tc_direct_parent(cur_tab); if(this.tc_layer_cache && this.tc_layer_cache.length >0) newtab = this.tc_layer_cache.pop(); else newtab = htr_new_layer(null,tabparent); - pageparent = tc_direct_parent(tabs[i].tabpage) + pageparent = tc_direct_parent(cur_tab.tabpage) newpage = htr_new_layer(null,pageparent); - newtab.marker_image = tabs[i].marker_image; + newtab.marker_image = cur_tab.marker_image; newtab.marker_image.src = '/sys/images/tab_lft3.gif'; $(newtab).find('span').text(' ' + htutil_encode(vals[j]) + ' '); //htr_write_content(newtab,content); @@ -343,8 +384,6 @@ function tc_updated(p1) htr_setvisibility(newpage,'inherit'); htr_setzindex(newtab,14); this.addTab(newtab,newpage,this,vals[j],'generated',''); - //setClipWidth(newtab,htr_getphyswidth(newtab)); - //setClipHeight(newtab,26); newpage.osrcdata = vals[j]; newpage.recordnumber = j; @@ -354,8 +393,8 @@ function tc_updated(p1) if(targettab) { - this.tabs[targettab].makeCurrent(); - this.tabs[targettab].tc_visible_changed('visible','hidden','inherit'); + tabs[targettab].makeCurrent(); + tabs[targettab].tc_visible_changed('visible','hidden','inherit'); } } } @@ -367,13 +406,20 @@ function tc_init(param) var l = param.layer; htr_init_layer(l,l,'tc'); ifc_init_widget(l); - l.tabs = new Array(); + l.tabs = []; l.addTab = tc_addtab; l.current_tab = null; l.init_tab = null; - l.tloc = param.tloc; - if (tc_tabs == null) tc_tabs = new Array(); + l.do_rendering = param.do_client_rendering; + l.select_x_offset = param.select_x_offset; + l.select_y_offset = param.select_y_offset; + l.xtoffset = param.xtoffset; + l.ytoffset = param.ytoffset; + l.tab_spacing = param.tab_spacing; + l.tab_h = param.tab_h; + if (tc_tabs == null) tc_tabs = []; tc_tabs[tc_tabs.length++] = l; + l.tloc = param.tloc; // Background color/image selection... l.main_bgColor = htr_extract_bgcolor(param.mainBackground); @@ -422,144 +468,99 @@ function tc_init(param) l.ChangeSelection2 = tc_changeselection_2; l.ChangeSelection3 = tc_changeselection_3; - // Movement geometries and clipping for tabs - switch(l.tloc) - { - case 0: // top - l.xo = +1; - l.yo = +2; - l.cl = "bottom"; - l.ci = -2; - break; - case 1: // bottom - l.xo = +1; - l.yo = -2; - l.cl = "top"; - l.ci = +2; - break; - case 2: // left - l.xo = +2; - l.yo = +1; - l.cl = "right"; - l.ci = -2; - break; - case 3: // right - l.xo = -2; - l.yo = +1; - l.cl = "left"; - l.ci = +2; - break; - case 4: // none - l.xo = 0; - l.yo = 0; - l.cl = "bottom"; - l.ci = 0; - break; - } return l; } -function tc_visible_changed(prop,o,n) +/*** Idk what this does... + *** + *** @param prop Unused, for some reason. + *** @param o Unused again... + *** @param n If this is true, it makes this.tab inherit visibility. Otherwise, hidden. + *** @returns nothing... idk what this is doing. + */ +function tc_visible_changed(prop, o, n) { - var t = this.tabctl; - var xo = t.xo; - var yo = t.yo; + const { tabctl: t } = this; + const { tabs, tloc } = t; + + if (tloc === 'None') console.warn("tc_visible_changed() called on tab contol with tab_location = none."); + if(n) htr_setvisibility(this.tab, 'inherit'); else htr_setvisibility(this.tab, 'hidden'); - // which tab should be selected? - if(htr_getvisibility(t.tabs[t.selected_index-1].tab)!='inherit') + + /** This nonsense is why we need goto in js. **/ + const pickSelectedTab = () => { - //try default tab - if(htr_getvisibility(t.init_tab.tab)=='inherit') + // If a visible tab is already selected, we're done. + const selected = tabs[t.selected_index-1]; + if (htr_getvisibility(selected.tab) === 'inherit') return; + + // Try to select the initial tab. + const initial = t.init_tab; + if (htr_getvisibility(initial.tab) === 'inherit') { - // This is forced, so we skip the obscure/reveal checks - t.ChangeSelection3(t.tabs[t.init_tab.tabindex-1]); - //t.tabs[t.init_tab.tabindex-1].makeCurrent(); - } - else //otherwise find first tab not hidden - { - for(var i=0; iCapabilities.Dom0NS && !s->Capabilities.Dom0IE &&(!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS)) { mssError(1,"HTTAB","NS4 or W3C DOM Support required"); return -1; } - - /** Get an id for this. **/ + + /** Reserve the next tab widget ID. **/ id = (HTTAB.idcnt++); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) + + /** Get the tab widget name. **/ + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) return -1; + strtcpy(name, ptr, sizeof(name)); + + /** Get x, y, w, & h of this object. **/ + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) x = 0; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) y = 0; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) { - mssError(0,"HTTAB","Tab widget must have a 'width' property"); + mssError(0, "HTTAB", "Tab widget must have a 'width' property"); return -1; } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) { - mssError(0,"HTTAB","Tab widget must have a 'height' property"); + mssError(0, "HTTAB", "Tab widget must have a 'height' property"); return -1; } - - /** Drop shadow **/ - shadow_offset=0; + + /** Get drop shadow data. **/ + shadow_offset = 0; if (wgtrGetPropertyValue(tree, "shadow_offset", DATA_T_INTEGER, POD(&shadow_offset)) == 0 && shadow_offset > 0) shadow_radius = shadow_offset+1; else @@ -128,25 +125,25 @@ httabRender(pHtSession s, pWgtrNode tree, int z) } if (wgtrGetPropertyValue(tree, "shadow_angle", DATA_T_INTEGER, POD(&shadow_angle)) != 0) shadow_angle = 135; - - /** Border radius, color, and style. **/ - if (wgtrGetPropertyValue(tree,"border_radius",DATA_T_INTEGER,POD(&border_radius)) != 0) - border_radius=0; - if (wgtrGetPropertyValue(tree,"border_color",DATA_T_STRING,POD(&ptr)) != 0) + + /** Get border info (radius, color, and style). **/ + if (wgtrGetPropertyValue(tree, "border_radius", DATA_T_INTEGER, POD(&border_radius)) != 0) + border_radius = 0; + if (wgtrGetPropertyValue(tree, "border_color", DATA_T_STRING, POD(&ptr)) != 0) strcpy(border_color, "#ffffff"); else strtcpy(border_color, ptr, sizeof(border_color)); - if (wgtrGetPropertyValue(tree,"border_style",DATA_T_STRING,POD(&ptr)) != 0) + if (wgtrGetPropertyValue(tree, "border_style", DATA_T_STRING,POD(&ptr)) != 0) strcpy(border_style, "outset"); else strtcpy(border_style, ptr, sizeof(border_style)); if (!strcmp(border_style, "none") || !strcmp(border_style, "hidden")) - border_width=0; + border_width = 0; else - border_width=1; - - /** Which side are the tabs on? **/ - if (wgtrGetPropertyValue(tree,"tab_location",DATA_T_STRING,POD(&ptr)) == 0) + border_width = 1; + + /** Get tab_location. **/ + if (wgtrGetPropertyValue(tree, "tab_location", DATA_T_STRING, POD(&ptr)) == 0) { if (!strcasecmp(ptr,"top")) tloc = Top; else if (!strcasecmp(ptr,"bottom")) tloc = Bottom; @@ -163,209 +160,532 @@ httabRender(pHtSession s, pWgtrNode tree, int z) { tloc = Top; } - - /** How wide should left/right tabs be? **/ - if (wgtrGetPropertyValue(tree,"tab_width",DATA_T_INTEGER,POD(&tab_width)) != 0) + + /** Count the number of tabs. **/ + tab_count = wgtrGetMatchingChildList(tree, "widget/tabpage", children, sizeof(children) / sizeof(pWgtrNode)); + + /** Get the selected tab. **/ + if (wgtrGetPropertyType(tree, "selected") == DATA_T_STRING && + wgtrGetPropertyType(tree, "selected_index") == DATA_T_INTEGER) { - if (tloc == Right || tloc == Left) + mssError(1,"HTTAB","%s: cannot specify both 'selected' and 'selected_index'", name); + return -1; + } + if (wgtrGetPropertyValue(tree, "selected", DATA_T_STRING, POD(&ptr)) == 0) + { + /** Search for the tab with the indicated name. **/ + for (i = 0; i < tab_count; i++) { - mssError(1,"HTTAB","%s: tab_width must be specified with tab_location of left or right", name); + char* tab_name; + wgtrGetPropertyValue(children[i], "name", DATA_T_STRING, POD(&tab_name)); + if (strcmp(ptr, tab_name) == 0) + { + /** sel_idx is 1 based, but i is 0 based. **/ + sel_idx = i + 1; + break; + } + } + if (i >= tab_count) { + mssError(1, "HTTAB", "%s: cannot find tab with name '%s'", name, ptr); + + /** Attempt to give hint. **/ + if (tab_count <= 0) return -1; + char* example_tab_name; + wgtrGetPropertyValue(children[0], "name", DATA_T_STRING, POD(&example_tab_name)); + mssError(0, "HTTAB", "Hint: 'selected' should be a tab name, such as \"%s\".", example_tab_name); + + /** Fail. **/ + return -1; + } + } + else if (wgtrGetPropertyValue(tree, "selected_index", DATA_T_INTEGER, POD(&sel_idx)) == 0) + { + if (sel_idx <= 0) + { + mssError(1, "HTTAB", "Invalid value for 'selected_index': %d.", sel_idx); + if (sel_idx == 0) mssError(0, "HTTAB", "Hint: 'selected_index' is 1-based."); + return -1; + } + if (sel_idx > tab_count) + { + mssError(1, "HTTAB", + "Invalid value for 'selected_index': %d. Tab control only has %d tab%s.", + sel_idx, tab_count, (tab_count == 1) ? "" : "s" + ); return -1; } } else { - if (tab_width < 0) tab_width = 0; + /** No specified selected tab, default to the first one. **/ + sel_idx = 1; } - - /** Which tab is selected? **/ - if (wgtrGetPropertyType(tree,"selected") == DATA_T_STRING && - wgtrGetPropertyValue(tree,"selected",DATA_T_STRING,POD(&ptr)) == 0) + + /** Handle user expressions for the selected tab. **/ + htrCheckAddExpression(s, tree, name, "selected"); + htrCheckAddExpression(s, tree, name, "selected_index"); + + /** Get the background color/image. **/ + htrGetBackground(tree, NULL, s->Capabilities.Dom2CSS, main_bg, sizeof(main_bg)); + + /** Get the inactive tab color/image. **/ + if (htrGetBackground(tree, "inactive", s->Capabilities.Dom2CSS, inactive_bg, sizeof(inactive_bg)) != 0) + strcpy(inactive_bg, main_bg); + + /** Get the text color. **/ + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0 + && !strpbrk(ptr, "{};&<>\"\'")) + strtcpy(text_color, ptr, sizeof(text_color)); + else + strcpy(text_color,"black"); + + /** Get the tab spacing and tab height. **/ + /** tab_w and tab_h are left as 0 if unset to tell the front end to calculate them dynamically. **/ + int tab_spacing = 2; /* Default to a 2px gap between tabs. */ + if (wgtrGetPropertyValue(tree, "tab_spacing", DATA_T_INTEGER, POD(&tmp)) == 0) tab_spacing = tmp; + if (wgtrGetPropertyValue(tree, "tab_width", DATA_T_INTEGER, POD(&tmp)) == 0) { - strtcpy(sel,ptr, sizeof(sel)); + if (tmp <= 0) + { + mssError(1, "HTTAB", "%s: 'tab_width' expected positive nonzero int, got %d.", name, tmp); + return -1; + } + tab_w = tmp; } - else + else if (tloc == Right || tloc == Left) { - strcpy(sel,""); + mssError(1, "HTTAB", "%s: 'tab_width' must be specified for 'tab_location' of left or right", name); + return -1; } - if (wgtrGetPropertyValue(tree,"selected_index", DATA_T_INTEGER, POD(&sel_idx)) != 0) + else { - sel_idx = -1; + /** Use a default value, updated client side. */ + tab_w = 80; + is_auto_tab_w = 1; } - if (sel_idx != -1 && *sel != '\0') + if (wgtrGetPropertyValue(tree, "tab_height", DATA_T_INTEGER, POD(&tmp)) == 0) { - mssError(1,"HTTAB","%s: cannot specify both 'selected' and 'selected_index'", name); - return -1; + if (tmp <= 0) + { + mssError(1, "HTTAB", "%s: 'tab_height' expected positive nonzero int, got %d.", name, tmp); + return -1; + } + tab_h = tmp; } - - /** User requesting expression for selected tab? **/ - htrCheckAddExpression(s, tree, name, "selected"); - - /** User requesting expression for selected tab using integer index value? **/ - htrCheckAddExpression(s, tree, name, "selected_index"); - - /** Background color/image? **/ - htrGetBackground(tree, NULL, s->Capabilities.Dom2CSS, main_bg, sizeof(main_bg)); - - /** Inactive tab color/image? **/ - htrGetBackground(tree, "inactive", s->Capabilities.Dom2CSS, inactive_bg, sizeof(inactive_bg)); - - /** Text color? **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(tab_txt, ptr, sizeof(tab_txt)); else - strcpy(tab_txt,"black"); - if (strpbrk(tab_txt, "{};&<>\"\'")) - strcpy(tab_txt,"black"); - - /** Determine offset to actual tab pages **/ - switch(tloc) { - case Top: xoffset = 0; yoffset = 24; xtoffset = 0; ytoffset = 0; break; - case Bottom: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = h; break; - case Right: xoffset = 0; yoffset = 0; xtoffset = w; ytoffset = 0; break; - case Left: xoffset = tab_width; yoffset = 0; xtoffset = 0; ytoffset = 0; break; - case None: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = 0; + /** Use default value, no client side calculation available. **/ + tab_h = 24; } - - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#tc%POSbase { background-position: 0px -24px; %STR }\n", id, main_bg); - - /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "tc%POSbase",id); - - /** Script include **/ + + /** Get macro selection translation values. **/ + /** CHANGE: Code previously used 1, but I think 0 is a better looking default. **/ + int along, out; + if (wgtrGetPropertyValue(tree, "select_translate_along", DATA_T_INTEGER, POD(&along)) != 0) along = 0; + if (wgtrGetPropertyValue(tree, "select_translate_out", DATA_T_INTEGER, POD(&out)) != 0) out = 2; + + /** Determine offset to actual tab pages and offsets for selected tabs. **/ + int select_x_offset = 0, select_y_offset = 0; + switch (tloc) + { + /*** Shift to cover boarder line: + *** Top: ytoffset +1 + *** Bottom: ytoffset -2 + *** Left: xtoffset +1 + *** Right: xtoffset -2 + ***/ + case Top: xoffset = 0; yoffset = tab_h; xtoffset = 0; ytoffset = 0; select_x_offset = -along; select_y_offset = -out; break; + case Bottom: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = h-1; select_x_offset = -along; select_y_offset = +out; break; + case Left: xoffset = tab_w; yoffset = 0; xtoffset = 0; ytoffset = 0; select_x_offset = -out; select_y_offset = -along; break; + case Right: xoffset = 0; yoffset = 0; xtoffset = w-1; ytoffset = 0; select_x_offset = +out; select_y_offset = -along; break; + case None: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = 0; select_x_offset = 0; select_y_offset = 0; break; + } + + /** Get coordinate-based selection translation values. **/ + if (wgtrGetPropertyValue(tree, "select_translate_x", DATA_T_INTEGER, POD(&tmp)) == 0) select_x_offset = tmp; + if (wgtrGetPropertyValue(tree, "select_translate_y", DATA_T_INTEGER, POD(&tmp)) == 0) select_y_offset = tmp; + + /*** Apply the opposite of the selection offset to all tabs. This + *** prevents the offset from causing the selected tab to appear + *** detached from the tab control. + ***/ + xtoffset -= select_x_offset; + ytoffset -= select_y_offset; + + /** Get the rendering type. **/ + /** Allows the developer to turn off JS client-side widget rendering for testing. **/ + int do_client_rendering = 1; + if (wgtrGetPropertyValue(tree, "rendering", DATA_T_STRING, POD(&ptr)) == 0) + { + if (strcmp(ptr, "server-side") == 0) do_client_rendering = 0; + else if (strcmp(ptr, "client-side") == 0) do_client_rendering = 1; + else + { + mssError(1, "HTTAB", "%s: Unknown value for 'rendering': %s", name, ptr); + mssError(0, "HTTAB", "HINT: Should be either 'server-side' or 'client-size'."); + return -1; + } + } + if (!do_client_rendering && tab_w == 0 && (tloc == Top || tloc == Bottom)) + { + /*** The dev has specified server-side rendering for Top/Bottom tabs + *** with dynamic width. This will probably look broken. + ***/ + mssError(1, "HTTAB", + "%s: 'rendering' value of \"server-side\" will break on tabs " + "with dynamic widths because they cannot be calculated server-side!", + name + ); + mssError(0, "HTTAB", "HINT: Specify 'tab_width' when using \"server-side\" rendering."); + } + + /** Handle DOM linkages. **/ + htrAddWgtrObjLinkage_va(s, tree, "tc%POSctrl", id); + + /** Include the htdrv_tab.js script. **/ htrAddScriptInclude(s, "/sys/js/htdrv_tab.js", 0); - - /** Add a global for the master tabs listing **/ + + /** Send globals variables to the client to avoid needing to hard code them. **/ + const int bufsiz = 96; + char* config_buf = nmSysMalloc(bufsiz); + if (config_buf == NULL) + { + mssError(1, "HTTAB", "%s: nmSysMalloc(%d) failed.", name, bufsiz); + return -1; + } + snprintf( + memset(config_buf, 0, bufsiz), bufsiz, + "{ tlocs: { Top:%d, Bottom:%d, Left:%d, Right:%d, None:%d } }", + Top, Bottom, Left, Right, None + ); + htrAddScriptGlobal(s, "tc_config", config_buf, HTR_F_VALUEALLOC); + /*** TODO: Greg - config_buf is definitely leaked because I can't + *** figure out how long it needs to remain in scope. + ***/ + + /** Add globals for the master tabs listing. **/ htrAddScriptGlobal(s, "tc_tabs", "null", 0); htrAddScriptGlobal(s, "tc_cur_mainlayer", "null", 0); - - /** Event handler for click-on-tab **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","tc","tc_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","tc","tc_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","tc","tc_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER","tc","tc_mouseover"); - + + /** Add mouse event handlers. **/ + htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tc", "tc_mousedown"); + htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tc", "tc_mouseup"); + htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tc", "tc_mousemove"); + htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tc", "tc_mouseover"); + /** Script initialization call. **/ - htrAddScriptInit_va(s," tc_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), tloc:%INT, mainBackground:\"%STR&JSSTR\", inactiveBackground:\"%STR&JSSTR\"});\n", - name, tloc, main_bg, inactive_bg); - + char tloc_name[8]; + switch (tloc) + { + case Top: strcpy(tloc_name, "Top"); break; + case Bottom: strcpy(tloc_name, "Bottom"); break; + case Left: strcpy(tloc_name, "Left"); break; + case Right: strcpy(tloc_name, "Right"); break; + case None: strcpy(tloc_name, "None"); break; + } + htrAddScriptInit_va(s, + "\ttc_init({" + "layer:wgtrGetNodeRef(ns,'%STR&SYM'), " + "tloc:'%STR', " + "mainBackground:'%STR&JSSTR', " + "inactiveBackground:'%STR&JSSTR', " + "select_x_offset:%INT, " + "select_y_offset:%INT, " + "xtoffset:%INT, " + "ytoffset:%INT, " + "tab_spacing:%INT, " + "tab_w:%INT, " + "tab_h:%INT, " + "do_client_rendering:%STR, " + "});\n", + name, + tloc_name, + main_bg, + inactive_bg, + select_x_offset, + select_y_offset, + xtoffset, + ytoffset, + tab_spacing, + (is_auto_tab_w) ? 0 : tab_w, /* 0 tells the front end that it should recalculate tab_w. */ + tab_h, + (do_client_rendering) ? "true" : "false" + ); + /** Check for tabpages within the tab control, to do the tabs at the top. **/ - tabcnt = wgtrGetMatchingChildList(tree, "widget/tabpage", children, sizeof(children)/sizeof(pWgtrNode)); if (tloc != None) { - for (i=0;i0, tab_width, - is_selected?(z+2):z, - (tloc==Bottom || tloc==Right)?0:border_radius, (tloc==Bottom || tloc==Left)?0:border_radius, (tloc==Top || tloc==Left)?0:border_radius, (tloc==Top || tloc==Right)?0:border_radius, - //(tloc==Top || tloc==Left)?0:border_radius, (tloc==Right)?0:border_radius, border_radius, (tloc==Bottom)?0:border_radius, - border_style, - (tloc!=Bottom)?1:0, (tloc!=Left)?1:0, (tloc!=Top)?1:0, (tloc!=Right)?1:0, - border_color, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color, - (tloc != Right)?"left":"right", - tab_txt, bg - ); - - htrAddBodyItem_va(s, "

%[ %STR&HTE %]%[ %STR&HTE %]

\n", - id, i+1, - tloc == Right, tabname, - is_selected?2:3, - tloc != Right, tabname - ); + case Top: case Bottom: i_offset_x = full_tab_spacing + tab_w; break; + case Right: case Left: i_offset_y = full_tab_spacing + tab_h; break; + case None:; /* Unreachable, but the compiler doesn't believe me. */ } - } - - /** h-2 and w-2 because w3c dom borders add to actual width **/ - htrAddBodyItem_va(s,"
\n", - id, h-border_width*2, w-border_width*2, x+xoffset, y+yoffset, z+1, - border_style, border_color, - (tloc==Top || tloc==Left)?0:border_radius, (tloc==Right)?0:border_radius, border_radius, (tloc==Bottom)?0:border_radius, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color + + /** Calculate tab flex information. **/ + /*** fl_x/y is enough flex to line up with the left/top of the tab + *** control. However, if the tab box changes size, tabs on the + *** right/bottom need to flex enough to handle that, too. + ***/ + double tab_fl_x = ht_get_fl_x(tree), tab_fl_y = ht_get_fl_y(tree); + if (tloc == Right) tab_fl_x += ht_get_fl_w(tree); + else if (tloc == Bottom) tab_fl_y += ht_get_fl_h(tree); + + /** Inject tab_fl values for client-side rendering. **/ + htrAddScriptInit_va(s, + "{" + "const node = wgtrGetNodeRef(ns,'%STR&SYM'); " + "node.tab_fl_x = %DBL; " + "node.tab_fl_y = %DBL; " + "}\n", + name, + tab_fl_x, + tab_fl_y + ); + + /** Loop over each tab. **/ + for (i = 0; i < tab_count; i++) + { + pWgtrNode tab = children[i]; + + /** Check if the tab is selected. **/ + int is_selected = (i == sel_idx - 1); + + /** Get type. **/ + wgtrGetPropertyValue(tab, "type", DATA_T_STRING, POD(&page_type)); + if (type == NULL || strcmp(type, "dynamic") != 0) strcpy(page_type, "static"); + + /** Use the tab title, defaulting to the tab name if it isn't specified. **/ + if (wgtrGetPropertyValue(tab, "title", DATA_T_STRING, POD(&tabname)) != 0) + wgtrGetPropertyValue(tab, "name", DATA_T_STRING, POD(&tabname)); + + /** Write tab CSS styles. **/ + int tab_x = (x + xtoffset) + (i_offset_x * i); + int tab_y = (y + ytoffset) + (i_offset_y * i); + htrAddStylesheetItem_va(s, + "\t#tc%POStab%POS { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[width:%POSpx; %]" /* Tab width has 0 flexibility. */ + "%[height:%POSpx; %]" /* Tab height has 0 flexibility. */ + "overflow:hidden; " + "z-index:%POS; " + "cursor:default; " + "border-radius:" + "%POSpx " + "%POSpx " + "%POSpx " + "%POSpx; " + "border-style:%STR&CSSVAL; " + "border-width:%POSpx %POSpx %POSpx %POSpx; " + "border-color:%STR&CSSVAL; " + "box-shadow:%DBLpx %DBLpx %POSpx %STR&CSSVAL; " + "text-align:%STR&CSSVAL; " + "color:%STR&CSSVAL; " + "font-weight:bold; " + "background-position: %INTpx %INTpx; " + "%STR " + "}\n", + id, i + 1, + ht_flex(tab_x, ht_get_parent_w(tree), tab_fl_x), // left + ht_flex(tab_y, ht_get_parent_h(tree), tab_fl_y), // top + (!is_auto_tab_w), tab_w, /* Tab width has 0 flexibility. */ + (tab_h > 0), tab_h, /* Tab height has 0 flexibility. */ + (is_selected) ? (z + 2) : z, + (tloc == Bottom || tloc == Right) ? 0 : border_radius, + (tloc == Bottom || tloc == Left) ? 0 : border_radius, + (tloc == Top || tloc == Left) ? 0 : border_radius, + (tloc == Top || tloc == Right) ? 0 : border_radius, + border_style, + (tloc != Bottom) ? 1 : 0, (tloc != Left) ? 1 : 0, (tloc != Top) ? 1 : 0, (tloc != Right) ? 1 : 0, + border_color, + sin(shadow_angle * M_PI/180) * shadow_offset, cos(shadow_angle * M_PI/180) * (-shadow_offset), shadow_radius, shadow_color, + (tloc != Right) ? "left" : "right", + text_color, + tab_x + 1, tab_y, + (is_selected) ? main_bg : inactive_bg ); - - /** Check for tabpages within the tab control entity, this time to do the pages themselves **/ - for (i=0;i" + "

" + "%[ %STR&HTE %]" + "" + "%[ %STR&HTE %]" + "

" + "
\n", + id, i + 1, (is_selected), + (tloc == Right), tabname, + (is_selected) ? 2 : 3, tab_h, + (tloc != Right), tabname + ); + } + } + + /** Write tab control CSS and HTML. **/ + htrAddStylesheetItem_va(s, + "#tc%POSctrl {" + "position:absolute; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "border-width:1px; " + "border-style:%STR&CSSVAL; " + "border-color:%STR&CSSVAL; " + "border-radius:" + "%POSpx " + "%POSpx " + "%POSpx " + "%POSpx; " + "box-shadow:%DBLpx %DBLpx %POSpx %STR&CSSVAL; " + "background-position:%INTpx %INTpx; " + "%STR " + "}\n", + id, + ht_flex_x(x + xoffset, tree), + ht_flex_y(y + yoffset, tree), + ht_flex_w(w - border_width * 2, tree), + ht_flex_h(h - border_width * 2, tree), + z + 1, + border_style, + border_color, + (tloc==Top || tloc==Left) ? 0 : border_radius, + (tloc==Right) ? 0 : border_radius, + border_radius, + (tloc==Bottom) ? 0 : border_radius, + sin(shadow_angle * M_PI/180) * shadow_offset, cos(shadow_angle * M_PI/180) * (-shadow_offset), shadow_radius, shadow_color, + x + xoffset, y + yoffset, + main_bg + ); + htrAddBodyItem_va(s, "
\n", id); + + /** Check for tab pages within the tab control entity, this time to do the pages themselves. **/ + for (i = 0; i < tab_count; i++) { - tabpage_obj = children[i]; - - htrCheckNSTransition(s, tree, tabpage_obj); - + pWgtrNode tab_page_tree = children[i]; + + /** Handle namespace transition. **/ + htrCheckNSTransition(s, tree, tab_page_tree); + /** First, render the tabpage and add stuff for it **/ - wgtrGetPropertyValue(tabpage_obj,"name",DATA_T_STRING,POD(&ptr)); - is_selected = (i+1 == sel_idx || (!*sel && i == 0) || !strcmp(sel,ptr)); - if(wgtrGetPropertyValue(tabpage_obj,"type",DATA_T_STRING,POD(&type)) != 0) - strcpy(page_type,"static"); - else if(!strcmp(type,"static") || !strcmp(type,"dynamic")) - strcpy(page_type,type); - else - strcpy(page_type,"static"); - strcpy(fieldname,""); - if(!strcmp(page_type,"dynamic")) - { - if(wgtrGetPropertyValue(tabpage_obj,"fieldname",DATA_T_STRING,POD(&field)) == 0) - strtcpy(fieldname,field,sizeof(fieldname)); - } - + wgtrGetPropertyValue(tab_page_tree,"name",DATA_T_STRING,POD(&ptr)); + + /** Check if the tab is selected. **/ + int is_selected = (i == sel_idx - 1); + + /** Set type. **/ + wgtrGetPropertyValue(tab_page_tree, "type", DATA_T_STRING, POD(&page_type)); + if (type == NULL || strcmp(type, "dynamic") != 0) strcpy(page_type, "static"); + + /** Set feildname. **/ + if (strcmp(page_type, "dynamic") == 0 && + wgtrGetPropertyValue(tab_page_tree, "fieldname", DATA_T_STRING, POD(&field)) == 0) + strtcpy(fieldname, field, sizeof(fieldname)); + else strcpy(fieldname, ""); + /** Add script initialization to add a new tabpage **/ if (tloc == None) - htrAddScriptInit_va(s," wgtrGetNodeRef('%STR&SYM', '%STR&SYM').addTab(null,wgtrGetContainer(wgtrGetNodeRef(\"%STR&SYM\",\"%STR&SYM\")),wgtrGetNodeRef('%STR&SYM','%STR&SYM'),'%STR&JSSTR','%STR&JSSTR','%STR&JSSTR');\n", + { + htrAddScriptInit_va(s, + "\twgtrGetNodeRef('%STR&SYM', '%STR&SYM')" + ".addTab(null, " + "wgtrGetContainer(wgtrGetNodeRef('%STR&SYM', '%STR&SYM')), " + "wgtrGetNodeRef('%STR&SYM', '%STR&SYM'), " + "'%STR&JSSTR', '%STR&JSSTR', '%STR&JSSTR'" + ");\n", + wgtrGetNamespace(tree), name, + wgtrGetNamespace(tab_page_tree), ptr, wgtrGetNamespace(tree), name, - wgtrGetNamespace(tabpage_obj), ptr, wgtrGetNamespace(tree), name, ptr,page_type,fieldname); + ptr, page_type, fieldname + ); + } else - htrAddScriptInit_va(s," wgtrGetNodeRef('%STR&SYM','%STR&SYM').addTab(htr_subel(wgtrGetParentContainer(wgtrGetNodeRef('%STR&SYM','%STR&SYM')),\"tc%POStab%POS\"),wgtrGetContainer(wgtrGetNodeRef(\"%STR&SYM\",\"%STR&SYM\")),wgtrGetNodeRef('%STR&SYM','%STR&SYM'),'%STR&JSSTR','%STR&JSSTR','%STR&JSSTR');\n", + { + htrAddScriptInit_va(s, + "\twgtrGetNodeRef('%STR&SYM','%STR&SYM')" + ".addTab(" + "htr_subel(" + "wgtrGetParentContainer(wgtrGetNodeRef('%STR&SYM', '%STR&SYM')), " + "'tc%POStab%POS'" + "), " + "wgtrGetContainer(wgtrGetNodeRef('%STR&SYM', '%STR&SYM')), " + "wgtrGetNodeRef('%STR&SYM', '%STR&SYM'), " + "'%STR&JSSTR', '%STR&JSSTR', '%STR&JSSTR'" + ");\n", wgtrGetNamespace(tree), name, wgtrGetNamespace(tree), name, - id, i+1, wgtrGetNamespace(tabpage_obj), ptr, wgtrGetNamespace(tree), name, ptr,page_type,fieldname); - - /** Add named global for the tabpage **/ - subnptr = nmSysStrdup(ptr); - htrAddWgtrObjLinkage_va(s, tabpage_obj, "tc%POSpane%POS", id, i+1); - htrAddWgtrCtrLinkage_va(s, tabpage_obj, "htr_subel(_parentobj, \"tc%POSpane%POS\")", id, i+1); - - /** Add DIV section for the tabpage. **/ - htrAddBodyItem_va(s,"
\n", - id,i+1,is_selected?"inherit":"hidden",w-2,z+2); - - /** Now look for sub-items within the tabpage. **/ - for (j=0;jChildren));j++) - htrRenderWidget(s, xaGetItem(&(tabpage_obj->Children), j), z+3); - - htrAddBodyItem(s, "
\n"); - - nmSysFree(subnptr); - - /** Add the visible property **/ - htrCheckAddExpression(s, tabpage_obj, ptr, "visible"); - - htrCheckNSTransitionReturn(s, tree, tabpage_obj); + id, i + 1, + wgtrGetNamespace(tab_page_tree), ptr, + wgtrGetNamespace(tree), name, + ptr, page_type, fieldname + ); + } + + /** Add named global for the tabpage. **/ + htrAddWgtrObjLinkage_va(s, tab_page_tree, "tc%POSpane%POS", id, i+1); + htrAddWgtrCtrLinkage_va(s, tab_page_tree, "htr_subel(_parentobj, \"tc%POSpane%POS\")", id, i + 1); + + /** Add DIV section to contane the tabpage. **/ + htrAddBodyItem_va(s, + "
\n", + id, i + 1, + (is_selected) ? "inherit" : "hidden", + z + 2 + ); + + /** Handle sub-items within the tabpage. **/ + for (int j = 0; j < xaCount(&(tab_page_tree->Children)); j++) + htrRenderWidget(s, xaGetItem(&(tab_page_tree->Children), j), z+3); + + /** Close the tab page container. */ + htrAddBodyItem(s, "
\n"); + + /** Add the visible property. **/ + htrCheckAddExpression(s, tab_page_tree, ptr, "visible"); + + /** Handle namespace transition. **/ + htrCheckNSTransitionReturn(s, tree, tab_page_tree); } - - /** Need to do other subwidgets (connectors, etc.) now **/ - htrRenderSubwidgets(s, tree, z+1); - + + /** Handle other subwidgets (connectors, etc.). **/ + htrRenderSubwidgets(s, tree, z + 1); + /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); - + htrAddBodyItem(s, "
\n"); + return 0; } @@ -385,25 +705,20 @@ httabInitialize() { pHtDriver drv; - /** Allocate the driver **/ + /** Tab Control Driver. **/ drv = htrAllocDriver(); - if (!drv) return -1; - - /** Fill in the structure. **/ - strcpy(drv->Name,"DHTML Tab Control Driver"); - strcpy(drv->WidgetName,"tab"); + if (drv == NULL) return -1; + strcpy(drv->Name, "DHTML Tab Control Driver"); + strcpy(drv->WidgetName, "tab"); drv->Render = httabRender; - /*xaAddItem(&(drv->PseudoTypes), "tabpage");*/ - - /** Register. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); + /** Tab Page Driver. **/ drv = htrAllocDriver(); - if (!drv) return -1; - strcpy(drv->Name,"DHTML Tab Page Driver"); - strcpy(drv->WidgetName,"tabpage"); + if (drv == NULL) return -1; + strcpy(drv->Name, "DHTML Tab Page Driver"); + strcpy(drv->WidgetName, "tabpage"); drv->Render = httabRender_page; htrRegisterDriver(drv); htrAddSupport(drv, "dhtml"); From 6ad367ac3e8570abfcf7b0ad8d1c00a07b532a5d Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 10:50:40 -0700 Subject: [PATCH 030/101] Make the clock widget responsive. Remove the moveable attribute. Simplify clock widget event code. Clean up cl_get_time(). --- centrallix-os/samples/clock.app | 3 - centrallix-os/sys/js/htdrv_clock.js | 62 ++++++-------------- centrallix/htmlgen/htdrv_clock.c | 88 +++++++++++++++++++++-------- 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/centrallix-os/samples/clock.app b/centrallix-os/samples/clock.app index 1a2567ab3..a17ceb2af 100644 --- a/centrallix-os/samples/clock.app +++ b/centrallix-os/samples/clock.app @@ -14,7 +14,6 @@ main "widget/page" shadowx = 2; shadowy = 2; size=1; - moveable="true"; bold="true"; } clock2 "widget/clock" @@ -22,7 +21,6 @@ main "widget/page" background="/sys/images/fade_pixelate_01.gif"; x=15; y=55; width=80; height=20; fgcolor1="white"; - moveable="true"; bold="true"; } clock3 "widget/clock" @@ -33,7 +31,6 @@ main "widget/page" fgcolor1="orange"; fgcolor2="#666666"; // size=3; - moveable="true"; bold="true"; } } diff --git a/centrallix-os/sys/js/htdrv_clock.js b/centrallix-os/sys/js/htdrv_clock.js index d7bec6027..612fdf452 100644 --- a/centrallix-os/sys/js/htdrv_clock.js +++ b/centrallix-os/sys/js/htdrv_clock.js @@ -24,7 +24,6 @@ function cl_init(param){ l.contentLayer = c1; l.hiddenLayer = c2; l.shadowed = param.shadowed; - l.moveable = param.moveable; l.bold = param.bold; l.fgColor1 = param.foreground1; l.fgColor2 = param.foreground2; @@ -57,13 +56,14 @@ function cl_init(param){ } function cl_get_time(l) { - var t = new Date(); - var time = new Object(); - time.hrs = t.getHours(); - time.mins = t.getMinutes(); - time.secs = t.getSeconds(); - time.msecs = t.getMilliseconds(); - time.formated = cl_format_time(l,time); + const t = new Date(); + const time = { + hrs: t.getHours(), + mins: t.getMinutes(), + secs: t.getSeconds(), + msecs: t.getMilliseconds(), + }; + time.formated = cl_format_time(l, time); return time; } @@ -110,46 +110,16 @@ function cl_format_time(l,t) { return timef; } -function cl_mouseup(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseUp'); - if (e.mainlayer.moveable) cl_move = false; - } - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mousedown(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseDown'); - if (e.mainlayer.moveable) { - cl_move = true; - cl_xOffset = e.pageX - getPageX(e.mainlayer); - cl_yOffset = e.pageY - getPageY(e.mainlayer); - } - } - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mouseover(e) { - if (e.kind == 'cl') cn_activate(e.mainlayer, 'MouseOver'); - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mouseout(e) { - if (e.kind == 'cl') cn_activate(e.mainlayer, 'MouseOut'); - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mousemove(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseMove'); - if (e.mainlayer.moveable && cl_move) - moveToAbsolute(e.mainlayer, - e.pageX - cl_xOffset, - e.pageY - cl_yOffset); - } +// Handle events by passing them on to Centrallix. +function cl_event(e, name) { + if (e && e.kind === 'cl') cn_activate(e.mainlayer, name); return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +function cl_mouseup(e) { return cl_event(e, 'MouseUp'); } +function cl_mousedown(e) { return cl_event(e, 'MouseDown'); } +function cl_mouseover(e) { return cl_event(e, 'MouseOver'); } +function cl_mouseout(e) { return cl_event(e, 'MouseOut'); } +function cl_mousemove(e) { return cl_event(e, 'MouseMove'); } // Load indication if (window.pg_scripts) pg_scripts['htdrv_clock.js'] = true; diff --git a/centrallix/htmlgen/htdrv_clock.c b/centrallix/htmlgen/htdrv_clock.c index 26d070321..4799127c0 100644 --- a/centrallix/htmlgen/htdrv_clock.c +++ b/centrallix/htmlgen/htdrv_clock.c @@ -64,7 +64,6 @@ htclRender(pHtSession s, pWgtrNode tree, int z) int shadowx = 0; int shadowy = 0; int size = 0; - int moveable = 0; int bold = 0; int showsecs = 1; int showampm = 1; @@ -142,10 +141,6 @@ htclRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"size",DATA_T_INTEGER,POD(&ptr)) == 0) size = (intptr_t)ptr; - /** Movable? **/ - if (wgtrGetPropertyValue(tree,"moveable",DATA_T_STRING,POD(&ptr)) == 0 && !strcmp(ptr,"true")) - moveable = 1; - /** Show Seconds **/ if (wgtrGetPropertyValue(tree,"seconds",DATA_T_STRING,POD(&ptr)) == 0 && (!strcasecmp(ptr,"false") || !strcasecmp(ptr,"no"))) showsecs = 0; @@ -160,19 +155,39 @@ htclRender(pHtSession s, pWgtrNode tree, int z) else fieldname[0]='\0'; - /** Write Style header items. **/ - htrAddStylesheetItem_va(s,"\t#cl%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#cl%POScon1 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+2); - htrAddStylesheetItem_va(s,"\t#cl%POScon2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+2); - - /** Write named global **/ + /** Write style headers. **/ + htrAddStylesheetItem_va(s, + "\t#cl%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ); + htrAddStylesheetItem_va(s, + "\t.cl%POScon { " + "position:absolute; " + "left:0px; " + "top:0px; " + "width:100%%; " + "z-index:%POS; " + "}\n", + id, + 0, + 0, + z + 2 + ); + + /** Setup linkage **/ htrAddWgtrObjLinkage_va(s, tree, "cl%POSbase",id); - /** Other global variables **/ - htrAddScriptGlobal(s, "cl_move", "false", 0); - htrAddScriptGlobal(s, "cl_xOffset", "null", 0); - htrAddScriptGlobal(s, "cl_yOffset", "null", 0); - /** Javascript include files **/ htrAddScriptInclude(s, "/sys/js/htdrv_clock.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); @@ -185,21 +200,46 @@ htclRender(pHtSession s, pWgtrNode tree, int z) htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "cl", "cl_mousemove"); /** Script initialization call. **/ - htrAddScriptInit_va(s, " cl_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), c1:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"cl%POScon1\"), c2:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"cl%POScon2\"), fieldname:\"%STR&JSSTR\", background:\"%STR&JSSTR\", shadowed:%POS, foreground1:\"%STR&JSSTR\", foreground2:\"%STR&JSSTR\", fontsize:%INT, moveable:%INT, bold:%INT, sox:%INT, soy:%INT, showSecs:%INT, showAmPm:%INT, milTime:%INT});\n", + htrAddScriptInit_va(s, + "cl_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "c1:htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'cl%POScon1'), " + "c2:htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'cl%POScon2'), " + "fieldname:'%STR&JSSTR', " + "background:'%STR&JSSTR', " + "shadowed:%POS, " + "foreground1:'%STR&JSSTR', " + "foreground2:'%STR&JSSTR', " + "fontsize:%INT, " + "bold:%INT, " + "sox:%INT, " + "soy:%INT, " + "showSecs:%INT, " + "showAmPm:%INT, " + "milTime:%INT, " + "});\n", name, name, id, name, id, - fieldname, main_bg, shadowed, - fgcolor1, fgcolor2, - size, moveable, bold, - shadowx, shadowy, - showsecs, showampm, miltime); + fieldname, + main_bg, + shadowed, + fgcolor1, + fgcolor2, + size, + bold, + shadowx, + shadowy, + showsecs, + showampm, + miltime + ); /** HTML body
element for the base layer. **/ htrAddBodyItem_va(s, "
\n",id); htrAddBodyItem_va(s, "
\n",main_bg,w,h); - htrAddBodyItem_va(s, "
\n",id); - htrAddBodyItem_va(s, "
\n",id); + htrAddBodyItem_va(s, "
\n", id, id); + htrAddBodyItem_va(s, " \n", id, id); htrAddBodyItem(s, "
\n"); /** Check for more sub-widgets **/ From 4411bd96b692216d4437d50dfdb8faac7d259783 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 11:05:09 -0700 Subject: [PATCH 031/101] Debug and clean up the component widget. Fix a bug where the container div could block mouse events. Add print failure warning when component-decl uses an empty gname value. Add TODOs to improve error handling once dups util code is available. Add comments. Improve formatting. Clean up. --- centrallix/htmlgen/htdrv_component.c | 147 +++++++++++++++-------- centrallix/htmlgen/htdrv_componentdecl.c | 35 +++++- 2 files changed, 128 insertions(+), 54 deletions(-) diff --git a/centrallix/htmlgen/htdrv_component.c b/centrallix/htmlgen/htdrv_component.c index 14988d1a4..c1460c2cf 100644 --- a/centrallix/htmlgen/htdrv_component.c +++ b/centrallix/htmlgen/htdrv_component.c @@ -185,7 +185,6 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) char sbuf[128]; pStruct params = NULL; pStruct old_params = NULL; - int i; char* path; int is_toplevel; int old_is_dynamic = 0; @@ -275,14 +274,51 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) params = htcmp_internal_CreateParams(s, tree); /** Any params have expressions? **/ - if (params) + if (params != NULL) { - for(i=0;inSubInf;i++) + for (int i = 0; i < params->nSubInf; i++) { + /** TODO: Israel - Add error handling. **/ htrCheckAddExpression(s, tree, name, params->SubInf[i]->Name); } } + + /** Write styles for the enclosing div. **/ + htrAddStylesheetItem_va(s, + "\t#cmp%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:visible; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ); + + /** Write enclosing div event CSS. **/ + htrAddStylesheetItem_va(s, + "\t#cmp%POSbase { " + "pointer-events:none; " + "}\n" + "\t#cmp%POSbase > * { " + "pointer-events:auto; " + "}\n", + id, id + ); + + /** Write enclosing div to isolate children. **/ + htrAddWgtrObjLinkage_va(s, tree, "cmp%POSbase", id); + htrAddBodyItem_va(s, "
\n", id); + /** If static mode, load the component **/ if (is_static) { @@ -305,13 +341,27 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Init component **/ htrAddScriptInit_va(s, - " cmp_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), is_static:true, allow_multi:false, auto_destroy:false, width:%INT, height:%INT, xpos:%INT, ypos:%INT});\n", - name, w,h,x,y); + "cmp_init({ " + "node:wgtrGetNodeRef(ns,\"%STR&SYM\"), " + "is_static:true, " + "allow_multi:false, " + "auto_destroy:false, " + "width:%INT, " + "height:%INT, " + "xpos:%INT, " + "ypos:%INT, " + "});\n", + name, + w, + h, + x, + y + ); /** Are there any templates we should use **/ memcpy(&wgtr_params, s->ClientInfo, sizeof(wgtr_params)); memset(wgtr_params.Templates, 0, sizeof(wgtr_params.Templates)); - for(i=0;i\n", id); /** Check param references **/ htcmp_internal_CheckReferences(cmp_tree, params, s->Namespace->DName); @@ -392,9 +418,6 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Switch the namespace back **/ htrLeaveNamespace(s); - /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); - /** End Init component **/ htrAddScriptInit_va(s, " cmp_endinit(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", name); @@ -411,41 +434,68 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) s->IsDynamic = old_is_dynamic; old_is_dynamic = 0; } + /** Dynamic mode, component is loaded by the client. **/ else { /** Init component **/ htrAddScriptInit_va(s, - " cmp_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), is_top:%POS, is_static:false, allow_multi:%POS, auto_destroy:%POS, path:\"%STR&JSSTR\", loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), \"cmp%POS\"), width:%INT, height:%INT, xpos:%INT, ypos:%INT});\n", - name, is_toplevel, allow_multi, auto_destroy, cmp_path, - name, id, - w, h, x, y); + "cmp_init({ " + "node:wgtrGetNodeRef(ns, '%STR&SYM'), " + "is_top:%POS, " + "is_static:false, " + "allow_multi:%POS, " + "auto_destroy:%POS, " + "path:'%STR&JSSTR', " + "loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'cmp%POS'), " + "width:%INT, " + "height:%INT, " + "xpos:%INT, " + "ypos:%INT, " + "});\n", + name, + is_toplevel, + allow_multi, + auto_destroy, + cmp_path, + name, id, + w, + h, + x, + y + ); - /** Add template paths **/ - for(i=0;inSubInf;i++) + for (int i = 0; i < params->nSubInf; i++) { - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").AddParam(\"%STR&SYM\",%[null%]%[\"%STR&HEX\"%]);\n", - name, params->SubInf[i]->Name, !params->SubInf[i]->StrVal, params->SubInf[i]->StrVal, - params->SubInf[i]->StrVal); + htrAddScriptInit_va(s, + "wgtrGetNodeRef(ns, '%STR&SYM').AddParam('%STR&SYM', %[null%]%['%STR&HEX'%]);\n", + name, params->SubInf[i]->Name, !params->SubInf[i]->StrVal, params->SubInf[i]->StrVal, params->SubInf[i]->StrVal + ); } } /** Dynamic mode -- load from client **/ htrAddWgtrCtrLinkage(s, tree, "_parentctr"); htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "cmp%POS", id, NULL, ""); - htrAddStylesheetItem_va(s,"\t#cmp%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n", id); + htrAddStylesheetItem_va(s,"\t#cmp%POS { position:absolute; visibility:hidden; left:0px; top:0px; width:0px; height:0px; z-index:0;}\n", id); } htrRenderSubwidgets(s, tree, z+1); + + /** End the enclosing div. **/ + htrAddBodyItem(s, "
\n"); rval = 0; @@ -507,4 +557,3 @@ htcmpInitialize() return 0; } - diff --git a/centrallix/htmlgen/htdrv_componentdecl.c b/centrallix/htmlgen/htdrv_componentdecl.c index 546b5cbb3..abd3f0998 100644 --- a/centrallix/htmlgen/htdrv_componentdecl.c +++ b/centrallix/htmlgen/htdrv_componentdecl.c @@ -89,7 +89,7 @@ htcmpdRender(pHtSession s, pWgtrNode tree, int z) int i, j; int rval = 0; int is_visual = 1; - char gbuf[256]; + char gbuf[256] = ""; char* gname; char* xptr; char* yptr; @@ -184,11 +184,37 @@ htcmpdRender(pHtSession s, pWgtrNode tree, int z) gname=""; htrAddWgtrCtrLinkage(s, tree, "_parentctr"); } + + /** Warning message. **/ + if (gname[0] == '\0') + { + fprintf(stderr, + "Warning: No value specified for gname, which is required to " + "be a valid symbol. Expect a printing failure.\n" + ); + } /** Init component **/ - htrAddScriptInit_va(s, " cmpd_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {vis:%POS, gns:%[\"%STR&SYM\"%]%[null%], gname:'%STR&SYM'%[, expe:'%STR&SYM'%]%[, expa:'%STR&SYM'%]%[, expp:'%STR&SYM'%]%[, applyhint:'%STR&SYM'%]});\n", - name, is_visual, *gbuf, gbuf, !*gbuf, gname, *expose_events_for, expose_events_for, *expose_actions_for, expose_actions_for, - *expose_props_for, expose_props_for, *apply_hints_to, apply_hints_to); + htrAddScriptInit_va(s, + "cmpd_init(wgtrGetNodeRef(ns, '%STR&SYM'), { " + "vis:%POS, " + "gns:%['%STR&SYM'%]%[null%], " + "gname:'%STR&SYM', " + "%[expe:'%STR&SYM', %]" + "%[expa:'%STR&SYM', %]" + "%[expp:'%STR&SYM', %]" + "%[applyhint:'%STR&SYM', %]" + "});\n", + name, + is_visual, + (gbuf[0] != '\0'), gbuf, (gbuf[0] == '\0'), + gname, + (expose_events_for[0] != '\0'), expose_events_for, + (expose_actions_for[0] != '\0'), expose_actions_for, + (expose_props_for[0] != '\0'), expose_props_for, + (apply_hints_to[0] != '\0'), apply_hints_to + ); + #if 0 for (i=0;iChildren));i++) { @@ -427,4 +453,3 @@ htcmpdInitialize() return 0; } - From 90d09fb3a1c3e2c332ba3b609124c6958ec765c6 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 11:17:25 -0700 Subject: [PATCH 032/101] Make the scrollpane widget responsive. Rewrite scrollpane widget C code. Add mouse wheel scrolling to scrollpanes. Add settings to control scrolling in htdrv_scrollpane.js. Add ability to use any widget inside a scrollpane. Add ScrollTo action to scrollpanes. Add Scroll, Wheel, and Click events to scrollpanes. Fix scroll events supposedly existing despite having no code to implement them. Move JS globals to be declared in JS, closer to where they are used. Refactor JS code to use more subfunctions leading to cleaner, clearer logic. Improve event filtering to allow wheel events from children inside the scollpane. Improve code consistency. --- centrallix-doc/Widgets/widgets.xml | 14 +- centrallix-os/sys/js/htdrv_scrollpane.js | 790 ++++++++++++++++------- centrallix/htmlgen/htdrv_scrollpane.c | 531 ++++++++------- 3 files changed, 866 insertions(+), 469 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index 315452e93..0ccb415b3 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -3394,21 +3394,31 @@ $Version=2$ - Scrolls to a specific location determined by the scroll bar. + Scrolls to a specific location determined by the scroll bar. Specify the 'Percent' attribute to indicate how far to scroll in decimal representation (so 1.00 is 100%, aka. the bottom of the page). Specify 'Offset' how many pixels the content should be offset from the top (specify 100 to scroll the first 100 px of content off the top of the scroll pane). Specify 'RangeStart' and 'RangeEnd' to scroll to within the pixel range (using the same units as offset). Keep in mind that this action will trigger a scroll event to occur. + + This event occurs any time the user scrolls the scroll pane. This includes scrolling by clicking the scroll buttons, clicking on the scroll bar, dragging the scroll thumb, turning the scroll wheel, or when the ScrollTo action is used. This event does not occur when the scroll pane moves because the contained content changed in length, or when the scroll pane is forced to scroll because the available visible area was resized. This event will never occur if the content within the scroll pane is shorter than the available visible area because then the content cannot be scrolled. + This event provides the :Percent attribute, a number from 0 to 100 (the same as the ScrollTo action above) representing the percentage that the user has now scrolled down the page as of the event occuring. This event also provides :Change, representing how much the user's scroll location has changed in the same unit as above (although this value will be negative if the user scrolled up). + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the scroll wheel while it is over the widget (or content inside the widget). The event will repeatedly fire each time the pointer moves. + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + This event occurs when the user moves the mouse pointer while it is over the widget (or content inside the widget). The event will repeatedly fire each time the pointer moves. This event occurs when the user moves the mouse pointer off of the widget. This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. This event occurs when the user releases the mouse button on the widget. + + Note: The Click, Wheel, MouseDown, and MouseUp events provide several pieces of useful information, including :shiftKey, :ctrlKey, :altKey, and :metaKey, which are 1 if the respective key is held down and 0 otherwise. These events also provide :button, a number representing the button number that the user used to execute the event (which appears to always be 0 for wheel). diff --git a/centrallix-os/sys/js/htdrv_scrollpane.js b/centrallix-os/sys/js/htdrv_scrollpane.js index ccbf0dba5..c83dba939 100644 --- a/centrallix-os/sys/js/htdrv_scrollpane.js +++ b/centrallix-os/sys/js/htdrv_scrollpane.js @@ -9,315 +9,615 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -function sp_init(param) + +/** ===== Config ===== **/ + +/** Whether to ignore zooming (ctrl + scroll) for scrolling events. **/ +const sp_ignore_zoom = true; + +/*** The overall scroll speed percentage modifier applied any time the scroll + *** wheel is used, in decimal representation (so 1.00 is 100%). + ***/ +const sp_scroll_mod = 1.00; + +/*** The scroll speed percentage modifier applied for fast scrolling (aka. + *** when using the scroll wheel while holding the alt key), using decimal + *** representation (so 1.00 is 100%). + *** + *** Note: Chromium-based browsers often use alt to scroll horizontally, so + *** fast scrolling may not function there. + ***/ +const sp_fast_scroll_mod = 8.00; + +/*** The scroll percentage modifier applied for inverted scrolling (aka. when + *** using the scroll wheel while holding down the shift key), using decimal + *** representation (so 1.00 is 100%). + *** + *** Set this to a positive number to disable inverted scrolling. + ***/ +const sp_inverted_scroll_mod = -1.00; + + + +/** ===== Globals ===== **/ + +/*** If a drag is in progress, stores the target image for the drag. Equals + *** undefined if no drag is in progress. + ***/ +let sp_drag_img = undefined; + +/*** If a button is pressed, stores the target image for the button so that it + *** can be reset when it is no longer pressed. Equals undefined if no button + *** is currently being pressed. + ***/ +let sp_button_img = undefined; + +/*** Stores the mainlayer that the mouse is currently over. Equals undefined if + *** the mouse is not over a scroll pane. + ***/ +let sp_target_mainlayer = undefined; + +/*** True if a click might be in progress. + *** + *** A click requires the user to generate a MouseDown event and a MouseUp + *** event in succession that are both on the scroll pane widget. + ***/ +let sp_click_in_progress = false; + + +/** Event handler to updates the thumb when the scroll pane resizes. **/ +const sp_handle_resize = ({ target: layer }) => layer.UpdateThumb(); +const sp_resize_observer = new ResizeObserver((entries) => entries.forEach(sp_handle_resize)); + +function sp_init({ layer: pane, area_name, thumb_name }) { - var l = param.layer; - var alayer=null; - var tlayer=null; - var ml; - var img; - var i; - if(cx__capabilities.Dom0NS) - { - var layers = pg_layers(l); - for(i=0;i maxBottom) maxBottom = bottom; + } + area.content_height = Math.max(0, Math.round(maxBottom - top)); + + /** Watch for changes in content height. **/ + if (cx__capabilities.Dom0IE) + { + area.runtimeStyle.clip.pane = pane; + // how to watch this in IE? + area.runtimeStyle.clip.onpropertychange = sp_WatchHeight; } else - { - alayer.clip.pane = l; - alayer.clip.watch("height",sp_WatchHeight); - } + { + area.clip.pane = pane; + area.clip.watch("height", sp_WatchHeight); + } + + /** Watch for changes in the size of the scroll pane. **/ + sp_resize_observer.observe(pane); } -/** @returns The height of the scrollpane content (including content outside the visibile area). **/ -// Replaces getClipHeight(area) + getClipTop(area) -function sp_get_content_height(area) { - return getClipHeight(area) + getClipTop(area); -} +/** ========== Getter functions ========== **/ +/** Functions to compute common values needed often in this code. **/ -/** @returns The height of visible area in the scrollpane. **/ -// Replaces getClipHeight(pane) -function sp_get_total_height(pane) { - return parseInt(window.getComputedStyle(pane).height); -} +/** @returns The height of content inside the scroll pane (even if not all of it is visible). **/ +function sp_get_content_height(area) + { + return area.content_height; + } -/** @returns The height of visible area in the scroll bar. **/ -function sp_get_sb_height(pane) { - return sp_get_total_height(pane) - (3*18); -} +/** @returns The height of visible area available to the scroll pane. **/ +function sp_get_available_height(pane) + { + return parseInt(getComputedStyle(pane).height); + } -function sp_action_scrollto(aparam) +/** @returns The height of the content outside the available visible area of the scroll pane. **/ +function sp_get_nonvisible_height(pane) { - var h = sp_get_content_height(this.area); // height of content - var ch = sp_get_total_height(this); - var d = h-ch; // height of non-visible content (max scrollable distance) - if (d < 0) d=0; - if (typeof aparam.Percent != 'undefined') - { - if (aparam.Percent < 0) aparam.Percent = 0; - else if (aparam.Percent > 100) aparam.Percent = 100; - setRelativeY(this.area, -d*aparam.Percent/100); - } - else if (typeof aparam.Offset != 'undefined') - { - if (aparam.Offset < 0) aparam.Offset = 0; - else if (aparam.Offset > d) aparam.Offset = d; - setRelativeY(this.area, -aparam.Offset); - } - else if (typeof aparam.RangeStart != 'undefined' && typeof aparam.RangeEnd != 'undefined') - { - var ny = -getRelativeY(this.area); - if (ny + ch < aparam.RangeEnd) ny = aparam.RangeEnd - ch; - if (ny > aparam.RangeStart) ny = aparam.RangeStart; - if (ny < 0) ny = 0; - if (ny > d) ny = d; - setRelativeY(this.area, -ny); - } - this.UpdateThumb(h); + return sp_get_content_height(pane.area) - sp_get_available_height(pane); } -function sp_WatchHeight(property, oldvalue, newvalue) +/** @returns The height of visible area available to the scroll bar. **/ +function sp_get_scrollbar_height(pane) { - if (cx__capabilities.Dom0IE) - { - newvalue = htr_get_watch_newval(window.event); - } - - // make sure region not offscreen now - newvalue += getClipTop(this.pane.area); - if (getRelativeY(this.pane.area) + newvalue < sp_get_total_height(this.pane)) { - setRelativeY(this.pane.area, sp_get_total_height(this.pane) - newvalue); + /** The up and down buttons and thumb are each 18px. **/ + return sp_get_available_height(pane) - (3*18); } - if (newvalue < sp_get_total_height(this.pane)) setRelativeY(this.pane.area, 0); - this.pane.UpdateThumb(newvalue); - newvalue -= getClipTop(this.pane.area); - this.bottom = this.top + newvalue; /* ns seems to unlink bottom = top + height if you modify clip obj */ - return newvalue; + +/** @returns The distance down that the scroll pane has been scrolled. **/ +function sp_get_scroll_dist(area) + { + return -getRelativeY(area); } -function sp_UpdateThumb(h) + +/*** Update the scroll pane to handle the height of its contained content + *** (aka. its child widgets) changing. + *** + *** @param property Unused + *** @param old_value The old height of the child content (unused). + *** @param new_value The new height of the child content. + ***/ +function sp_WatchHeight(property, old_value, new_value) { - /** 'this' is a spXpane **/ - if(!h) - { /** if h is supplied, it is the soon-to-be clip.height of the spXarea **/ - h=sp_get_content_height(this.area); // height of content - } - var d=h-sp_get_total_height(this); // height of non-visible content (max scrollable distance) - var v=sp_get_sb_height(this); - if(d<=0) - setRelativeY(this.thum, 18); - else - setRelativeY(this.thum, 18+v*(-getRelativeY(this.area)/d)); + const { pane } = this; + const { area } = pane; + + /** Handle legacy Internet Explorer behavior. **/ + if (cx__capabilities.Dom0IE) + new_value = htr_get_watch_newval(window.event); + + /** Update the internal content height value. **/ + area.content_height = new_value; + + /** Get the available height of the visible area. **/ + const available_height = sp_get_available_height(pane); + + /** Scroll to the top if the content is now smaller than the visible area. **/ + if (getRelativeY(area) + new_value < available_height) + setRelativeY(area, available_height - new_value); + if (new_value < available_height) setRelativeY(area, 0); + + /** Update the scroll thumb. **/ + pane.UpdateThumb(); + + return new_value; } -function do_mv() +/** Called when the ScrollTo action is used. **/ +function sp_action_ScrollTo({ Percent, Offset, RangeStart, RangeEnd }) { + const pane = this; + const available = sp_get_available_height(pane); + const nonvisible_height = sp_get_nonvisible_height(pane); // Height of non-visible content. + + /** Ignore the action if all content is visible. **/ + if (nonvisible_height <= 0) return; + + /** Calculate the new location to scroll to. **/ + const new_scroll_height = + (Offset !== undefined) ? Offset : + (Percent !== undefined) ? Math.clamp(0, Percent / 100, 1) * nonvisible_height : + (RangeStart !== undefined && RangeEnd !== undefined) ? + Math.clamp(RangeEnd - available, sp_get_scroll_dist(pane.area), RangeStart) : + 0; /* Fallback default value. */ + + /** Scroll to the new location. **/ + sp_scroll_to(pane, new_scroll_height); + } + +/** Recalculate and update the location of the scroll thumb. **/ +function sp_update_thumb() + { + const pane = this; + const { area, thumb } = pane; - var ti=sp_target_img; - /** not sure why, but it's getting called with a null sp_target_img sometimes... **/ - if(!ti) + /** Get the height of nonvisible content. **/ + const nonvisible_height = sp_get_nonvisible_height(pane); + + /** Handle the case where all content is visible. **/ + if (nonvisible_height <= 0) { + /** All we need to do is to move the scroll thumb to the top. **/ + setRelativeY(thumb, 18); return; } - var h=sp_get_content_height(ti.area); // height of content - var d=h-sp_get_total_height(ti.pane); // height of non-visible content (max scrollable distance) - var incr=sp_mv_incr; - if(d<0) - incr=0; - if (ti.kind=='sp') + + /** Calculate where the scroll thumb should be based on the scroll progress. **/ + let scroll_dist = sp_get_scroll_dist(area); + if (scroll_dist > nonvisible_height) { - var scrolled = -getRelativeY(ti.area); // distance scrolled already - if(incr > 0 && scrolled+incr>d) - incr=d-scrolled; + /** Scroll down to fill the new space at the bottom of the scroll pane. **/ + setRelativeY(area, -nonvisible_height); + scroll_dist = nonvisible_height; + } + const progress = scroll_dist / nonvisible_height; + const progress_scaled = 18 + sp_get_scrollbar_height(pane) * progress; + + /** Set the scroll thumb to the calculated location. **/ + setRelativeY(thumb, progress_scaled); + } - /** if we've scrolled down less than we want to go up, go up the distance we went down **/ - if(incr < 0 && scrolled<-incr) - incr=-scrolled; +/*** Scroll the scroll pane to the specified `scroll_height`. + *** + *** @param pane The affected scroll pane DOM node. + *** @param scroll_height The new height, in pixels, that the content should + *** be scrolled to as a result of this scroll. + ***/ +function sp_scroll_to(pane, scroll_height) + { + /** Ignore undefined target pane. **/ + if (!pane) return; + + /** Don't scroll if there's no content to scroll. **/ + const nonvisible_height = sp_get_nonvisible_height(pane); + if (nonvisible_height < 0) return; + + /** Save the current scroll amount for later. **/ + const scroll_height_old = sp_get_scroll_dist(pane.area); + + /** Clamp the scroll height within the bounds of the scroll bar. **/ + const scroll_height_new = Math.clamp(0, scroll_height, nonvisible_height); + + /** Update the content. **/ + setRelativeY(pane.area, -scroll_height_new); + pane.UpdateThumb(); + + /** Construct the param for the centrallix 'Scroll' event. **/ + const percent_old = (scroll_height_old / nonvisible_height) * 100; + const percent_new = (scroll_height_new / nonvisible_height) * 100; + const param = { Percent: percent_new, Change: percent_new - percent_old }; + + /** Schedule the scroll event to allow the page to repaint first. **/ + setTimeout(() => cn_activate(pane, 'Scroll', param), 0); + } - /*var layers = pg_layers(ti.pane); - for(var i=0;i getPageY(ti.pane)+18+v) new_y=getPageY(ti.pane)+18+v; - if (new_y < getPageY(ti.pane)+18) new_y=getPageY(ti.pane)+18; - setPageY(ti.thum,new_y); - var h=sp_get_content_height(ti.area); - var d=h-sp_get_total_height(ti.pane); - if (d<0) d=0; - var yincr = (((getRelativeY(ti.thum)-18)/v)*-d) - getRelativeY(ti.area); - moveBy(ti.area, 0, yincr); - return EVENT_HALT | EVENT_PREVENT_DEFAULT_ACTION; - } + /** Trigger Centrallix events. **/ + const pane = sp_get_pane(e); + if (pane) cn_activate(pane, 'MouseMove'); + + /** Monitor events on other DOM nodes to detect MouseOut. **/ + if (!pane && sp_target_mainlayer) + { + /** Mouse out has occurred, ending the mouse over. **/ + cn_activate(sp_target_mainlayer, 'MouseOut'); + sp_target_mainlayer = undefined; + } + + /** Check if a drag is in progress (aka. the drag target exists and is a scroll thumb). **/ + const target_img = sp_drag_img; + if (target_img && target_img.kind === 'sp' && target_img.name === 't') + { + const { pane } = target_img; + + /** Get drag_dist: the distance that the scroll bar should move. **/ + const page_y = getPageY(target_img.thumb); + const drag_dist = e.pageY - page_y; + + /** Scale drag_dist to the distance that the content should move. **/ + const scrollbar_height = sp_get_scrollbar_height(pane); + const nonvisible_height = sp_get_nonvisible_height(pane); + const content_drag_dist = (drag_dist / scrollbar_height) * nonvisible_height; + + /** Scroll the content by the required amount to reach the new mouse location. **/ + sp_scroll(target_img.pane, content_drag_dist); + + /** Event handled. **/ + return EVENT_HALT | EVENT_PREVENT_DEFAULT_ACTION; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +/*** Handles mouse up events anywhere on the page. + *** + *** @param e The event that has occurred. + *** @returns An event result (see ht_render.js). + ***/ function sp_mouseup(e) { - if (sp_mv_timeout != null) - { - clearTimeout(sp_mv_timeout); - sp_mv_timeout = null; - sp_mv_incr = 0; - } - if (sp_target_img != null) - { - if (sp_target_img.name != 'b') - pg_set(sp_target_img,'src',htutil_subst_last(sp_target_img.src,"b.gif")); - sp_target_img = null; - } - if (e.kind == 'sp') cn_activate(e.mainlayer, 'MouseUp'); + /** Trigger Centrallix events. **/ + if (e.kind === 'sp') + { + const params = sp_get_event_params(e); + cn_activate(e.mainlayer, 'MouseUp', params); + if (sp_click_in_progress) + cn_activate(e.mainlayer, 'Click', params); + } + + /** A click is no longer in progress. **/ + sp_click_in_progress = false; + + /** Check for an active drag. **/ + if (sp_drag_img) + { + /** End the drag. **/ + sp_drag_img = undefined; + } + + /** Check for a pressed button. **/ + if (sp_button_img) + { + /** Reset the pressed button. **/ + pg_set(sp_button_img, 'src', htutil_subst_last(sp_button_img.src, "b.gif")); + sp_button_img = undefined; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +/*** Handles mouse over events anywhere on the page. + *** + *** @param e The event that has occurred. + *** @returns An event result (see ht_render.js). + ***/ function sp_mouseover(e) { - if (e.kind == 'sp') - { - if (!sp_cur_mainlayer) - { - cn_activate(e.mainlayer, 'MouseOver'); - sp_cur_mainlayer = e.mainlayer; - } - } + /** Check for mouse over on an sp element. **/ + if (sp_target_mainlayer && e.kind === 'sp') + { + /** Begin a mouse over. **/ + cn_activate(e.mainlayer, 'MouseOver'); + sp_target_mainlayer = e.mainlayer; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } -// Load indication +/** Indicate loading is complete. **/ if (window.pg_scripts) pg_scripts['htdrv_scrollpane.js'] = true; diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index e5c061d16..2a4b36154 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -1,15 +1,3 @@ -#include -#include -#include -#include -#include "ht_render.h" -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/xarray.h" -#include "cxlib/xhash.h" -#include "cxlib/mtsession.h" -#include "cxlib/strtcpy.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ @@ -42,6 +30,19 @@ /* layer. Can contain most objects, except for framesets. */ /************************************************************************/ +#include +#include +#include +#include + +#include "cxlib/mtask.h" +#include "cxlib/mtsession.h" +#include "cxlib/strtcpy.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "ht_render.h" +#include "obj.h" + /** globals **/ static struct @@ -57,267 +58,351 @@ int htspaneRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; - char name[64]; - int x,y,w,h; - int id, i; - int visible = 1; - char bcolor[64] = ""; - char bimage[64] = ""; - - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE &&!(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + + if (!(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS) && !s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE) { - mssError(1,"HTSPANE","Netscape DOM or W3C DOM1 HTML and DOM2 CSS support required"); + mssError(1, "HTSPANE", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS or IE DOM or Netscape DOM required."); return -1; } - - /** Get an id for this. **/ - id = (HTSPANE.idcnt++); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) + + /** Get an id for this scrollpane. **/ + const int id = (HTSPANE.idcnt++); + + /** Get name. **/ + char name[64]; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have an 'x' property"); + mssError(1, "HTSPANE", "widget/scrollpane must have a 'name' property."); return -1; } - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) + strtcpy(name, ptr, sizeof(name)); + + /** Get x, y, w, & h. **/ + int x, y, w, h; + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'y' property"); + mssError(1, "HTSPANE", "widget/scrollpane must have an 'x' property."); return -1; } - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'width' property"); + mssError(1, "HTSPANE", "widget/scrollpane must have a 'y' property."); return -1; } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'height' property"); + mssError(1, "HTSPANE", "widget/scrollpane must have a 'width' property."); return -1; } - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Check background color **/ - if (wgtrGetPropertyValue(tree,"bgcolor",DATA_T_STRING,POD(&ptr)) == 0) + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) { - strtcpy(bcolor,ptr,sizeof(bcolor)); + mssError(1, "HTSPANE", "widget/scrollpane must have a 'height' property."); + return -1; } - if (wgtrGetPropertyValue(tree,"background",DATA_T_STRING,POD(&ptr)) == 0) + + /** Get the background color or image. **/ + char background_color[64] = ""; + if (wgtrGetPropertyValue(tree, "bgcolor", DATA_T_STRING,POD(&ptr)) == 0) { - strtcpy(bimage,ptr,sizeof(bimage)); + strtcpy(background_color, ptr, sizeof(background_color)); } - - /** Marked not visible? **/ - if (wgtrGetPropertyValue(tree,"visible",DATA_T_STRING,POD(&ptr)) == 0) + char background_image[64] = ""; + if (wgtrGetPropertyValue(tree, "background", DATA_T_STRING, POD(&ptr)) == 0) { - if (!strcmp(ptr,"false")) visible = 0; + strtcpy(background_image, ptr, sizeof(background_image)); } - - /** Ok, write the style header items. **/ - if (s->Capabilities.Dom0NS) + + /** Get visibility. **/ + int visible = 1; + if (wgtrGetPropertyValue(tree, "visible", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddStylesheetItem_va(s, - "\t#sp%POSpane { " - "POSITION:absolute; " - "VISIBILITY:%STR; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "HEIGHT:"ht_flex_format"; " - "OVERFLOW: clip; " - "Z-INDEX:%POS; " - "}\n", - id, - (visible) ? "inherit" : "hidden", - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), - z - ); - htrAddStylesheetItem_va(s, - "\t#sp%POSarea { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:0px; " - "TOP:0px; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " - "}\n", - id, - ht_flex(w - 18, ht_get_total_w(tree), 1.0), - z + 1 - ); - htrAddStylesheetItem_va(s, - "\t#sp%POSthum { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:18px; " - "WIDTH:18px; " - "Z-INDEX:%POS; " - "}\n", - id, - ht_flex(w - 18, tree->width, 1.0), - z + 1 - ); + if (strcmp(ptr, "false") == 0) visible = 0; } - - /** Write globals for internal use **/ - htrAddScriptGlobal(s, "sp_target_img", "null", 0); - htrAddScriptGlobal(s, "sp_click_x","0",0); - htrAddScriptGlobal(s, "sp_click_y","0",0); - htrAddScriptGlobal(s, "sp_thum_y","0",0); - htrAddScriptGlobal(s, "sp_mv_timeout","null",0); - htrAddScriptGlobal(s, "sp_mv_incr","0",0); - htrAddScriptGlobal(s, "sp_cur_mainlayer","null",0); - - /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "sp%POSpane",id); + + /** DOM Linkages. **/ htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, \"sp%POSarea\")",id); - + htrAddWgtrObjLinkage_va(s, tree, "sp%POSpane",id); + + /** Include scrollpane script and its dependencies. **/ htrAddScriptInclude(s, "/sys/js/htdrv_scrollpane.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - - htrAddScriptInit_va(s," sp_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), aname:\"sp%POSarea\", tname:\"sp%POSthum\"});\n", name,id,id); - - /** HTML body
elements for the layers. **/ - if(s->Capabilities.Dom0NS) + + /** Init the JS scripts. **/ + htrAddScriptInit_va(s, + "\tsp_init({" + "layer: wgtrGetNodeRef(ns, '%STR&SYM'), " + "area_name: 'sp%POSarea', " + "thumb_name: 'sp%POSthumb', " + "});\n", + name, + id, + id + ); + + /** Write html and styles. **/ + if (s->Capabilities.Dom1HTML) { - htrAddBodyItem_va(s,"
", id, *bcolor, bcolor, *bimage, bimage, w); - htrAddBodyItem(s, "
"); - htrAddBodyItem_va(s,"",h-36); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem_va(s,"
\n
",id,id,w-2,h-2); - } - else if(s->Capabilities.Dom1HTML) - { - //htrAddStylesheetItem_va(s,"\t#sp%dpane { POSITION:absolute; VISIBILITY:%s; LEFT:%dpx; TOP:%dpx; WIDTH:%dpx; HEIGHT:%dpx; clip:rect(0px,%dpx,%dpx,0px); Z-INDEX:%d; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); - //htrAddStylesheetItem_va(s,"\t#sp%darea { HEIGHT: %dpx; WIDTH:%dpx; }\n",id, h, w-18); - //htrAddStylesheetItem_va(s,"\t#sp%dthum { POSITION:absolute; VISIBILITY:inherit; LEFT:%dpx; TOP:18px; WIDTH:18px; Z-INDEX:%dpx; }\n",id,w-18,z+1); + /** Write the scrollpane. **/ htrAddBodyItem_va(s, - "
\n", id, (visible) ? "inherit" : "hidden", - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), z ); - htrAddBodyItem_va(s,"", id); - htrAddBodyItem_va(s,"", id); - htrAddBodyItem_va(s,"", id); + + /** Write shared CSS for the following UI elements. **/ htrAddStylesheetItem_va(s, - "\t#sp%POSup { " - "POSITION:absolute; " - "LEFT:"ht_flex_format"; " - "TOP:0px; " + "\t.sp%POS_scroll { " + "position:absolute; " + "left:"ht_flex_format"; " "}\n", id, ht_flex(w - 18, tree->width, 1.0) ); - htrAddStylesheetItem_va(s, - "\t#sp%POSbar { " - "POSITION:absolute; " - "LEFT:"ht_flex_format"; " - "TOP:18px; " - "WIDTH:18px; " - "HEIGHT:"ht_flex_format"; " - "}\n", + + /** Write the up button. **/ + htrAddBodyItem_va(s, + "", + id, + id + ); + + /** Write the scroll bar. **/ + htrAddBodyItem_va(s, + "", + id, id, - ht_flex(w - 18, tree->width, 1.0), ht_flex(h - 36, tree->height, 1.0) ); - htrAddStylesheetItem_va(s, - "\t#sp%POSdown { " - "POSITION:absolute; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "}\n", + + /** Write the down button. **/ + htrAddBodyItem_va(s, + "", + id, id, - ht_flex(w - 18, tree->width, 1.0), ht_flex(h - 18, tree->height, 1.0) ); + + /** Write the scroll thumb. **/ htrAddBodyItem_va(s, - "
" - "" - "
\n", + "" + "
\n", + id, id, - ht_flex(w - 18, tree->width, 1.0), z + 1 ); + + /** Write the scroll area. **/ htrAddBodyItem_va(s, - "
", id, - ht_flex(w - 18, ht_get_total_w(tree), 1.0), - ht_flex(h, ht_get_total_h(tree), 1.0), + ht_flex(w - 18, ht_get_parent_w(tree), 1.0), + ht_flex(h, ht_get_parent_h(tree), 1.0), z + 1 ); } + else if (s->Capabilities.Dom0NS) + { + /** Write CSS for everything. **/ + htrAddStylesheetItem_va(s, + "\t#sp%POSpane { " + "position:absolute; " + "visibility:%STR; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "overflow:clip; " + "z-index:%POS; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSarea { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex(w - 18, ht_get_parent_w(tree), 1.0), + z + 1 + ); + htrAddStylesheetItem_va(s, + "\t#sp%POSthumb { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:18px; " + "width:18px; " + "z-index:%POS; " + "}\n", + id, + ht_flex(w - 18, tree->width, 1.0), + z + 1 + ); + + /** Write the scrollpane. **/ + htrAddBodyItem_va(s, + "
" + "", + id, + *background_color, background_color, + *background_image, background_image, + w + ); + + htrAddBodyItem_va(s, + /** Write the up button. **/ + "" + + /** Write the scroll bar. **/ + "" + + /** Write the down button. **/ + "" + + /** Close the scrollpane table (see above). **/ + "
" + "" + "
" + "" + "
" + "" + "
\n", + h - 36 + ); + + /** Write the scroll thumb. **/ + htrAddBodyItem_va(s, + "
" + "" + "
\n", + id + ); + + /** Write the scroll area. **/ + htrAddBodyItem_va(s, + "
" + "
", + id, + w - 2, + h - 2 + ); + } else { - mssError(1,"HTSPNE","Browser not supported"); + mssError(1, "HTSPNE", "Browser not supported!!"); } - - /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","sp","sp_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","sp","sp_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","sp","sp_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sp","sp_mouseover"); - - /** Do subwidgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+2); - - /** Finish off the last
**/ - if(s->Capabilities.Dom0NS) + + /** Render children/subwidgets. **/ + for (int i = 0; i < xaCount(&(tree->Children)); i++) + htrRenderWidget(s, xaGetItem(&(tree->Children), i), z + 2); + + /** Close the final
s. **/ + if (s->Capabilities.Dom1HTML) { - htrAddBodyItem(s,"
\n"); + htrAddBodyItem(s,"
\n"); } - else if(s->Capabilities.Dom1HTML) + else if (s->Capabilities.Dom0NS) { - htrAddBodyItem(s,"
\n"); + htrAddBodyItem(s,"
\n"); } else { - mssError(1,"HTSPNE","browser not supported"); + mssError(1, "HTSPNE", "Browser not supported!!"); } - + + /** Add the event handling scripts **/ + htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "sp", "sp_mousedown"); + htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "sp", "sp_mousemove"); + htrAddEventHandlerFunction(s, "document", "MOUSEUP", "sp", "sp_mouseup"); + htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sp", "sp_mouseover"); + htrAddEventHandlerFunction(s, "document", "WHEEL", "sp", "sp_wheel"); + return 0; } @@ -328,29 +413,31 @@ int htspaneInitialize() { pHtDriver drv; - - /** Allocate the driver **/ + + /** Allocate the driver **/ drv = htrAllocDriver(); if (!drv) return -1; - + /** Fill in the structure. **/ - strcpy(drv->Name,"HTML ScrollPane Widget Driver"); + strcpy(drv->Name,"HTML Widget/scrollpane Driver"); strcpy(drv->WidgetName,"scrollpane"); drv->Render = htspaneRender; - + /** Events **/ - htrAddEvent(drv,"Click"); - htrAddEvent(drv,"MouseUp"); - htrAddEvent(drv,"MouseDown"); - htrAddEvent(drv,"MouseOver"); - htrAddEvent(drv,"MouseOut"); - htrAddEvent(drv,"MouseMove"); - + htrAddEvent(drv, "Scroll"); + htrAddEvent(drv, "Click"); + htrAddEvent(drv, "Wheel"); + htrAddEvent(drv, "MouseUp"); + htrAddEvent(drv, "MouseDown"); + htrAddEvent(drv, "MouseOver"); + htrAddEvent(drv, "MouseOut"); + htrAddEvent(drv, "MouseMove"); + /** Register. **/ htrRegisterDriver(drv); - + htrAddSupport(drv, "dhtml"); HTSPANE.idcnt = 0; - + return 0; } From 8feae299d56315b57254ebcd3192a409b37d535c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 11:22:43 -0700 Subject: [PATCH 033/101] Make the button widget responsive. Replace JS animation with a faster, responsive CSS implementation. Remove unnecessary clip rectangles. Fix style issues. --- centrallix-os/sys/js/htdrv_button.js | 3 -- centrallix/htmlgen/htdrv_button.c | 70 ++++++++++++++-------------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_button.js b/centrallix-os/sys/js/htdrv_button.js index 6be26b436..2efb27773 100644 --- a/centrallix-os/sys/js/htdrv_button.js +++ b/centrallix-os/sys/js/htdrv_button.js @@ -262,7 +262,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage') return; } - moveTo(layer,layer.orig_x,layer.orig_y); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','0px',null); @@ -294,7 +293,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage' ) return; } - moveTo(layer,layer.orig_x,layer.orig_y); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','1px',null); @@ -337,7 +335,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage') return; } - moveTo(layer,layer.orig_x+1,layer.orig_y+1); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','1px',null); diff --git a/centrallix/htmlgen/htdrv_button.c b/centrallix/htmlgen/htdrv_button.c index 1779b54be..3e3f7c577 100644 --- a/centrallix/htmlgen/htdrv_button.c +++ b/centrallix/htmlgen/htdrv_button.c @@ -152,12 +152,12 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s, "\t#gb%POSpane { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), @@ -165,6 +165,14 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), z ); + + /** Button click animation. **/ + if (is_enabled) { + htrAddStylesheetItem_va(s, + "\t#tb%POSpane:active { transform: translate(1px, 1px); }\n", + id + ); + } htrAddScriptGlobal(s, "gb_cur_img", "null", 0); htrAddScriptGlobal(s, "gb_current", "null", 0); @@ -208,12 +216,12 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s, "\t#gb%POSpane2 { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:%POS; " - "TOP:%POS; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "left:%POS; " + "top:%POS; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, 0, @@ -337,26 +345,18 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s, "\t#gb%POSpane { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "OVERFLOW:hidden; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "CLIP:rect(" - "0, " - ht_flex_format", " - ht_flex_format", " - "0" - "); " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(w-1-2*box_offset+2*clip_offset, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(h-1-2*box_offset+2*clip_offset, tree->Parent->height, ht_get_fl_h(tree)), z ); htrAddStylesheetItem_va(s,"\t#gb%POSpane2, #gb%POSpane3 { height: "ht_flex_format";}\n",id,id,ht_flex(h-3,tree->Parent->height,ht_get_fl_h(tree))); @@ -366,20 +366,18 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s, "\t#gb%POSpane { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "OVERFLOW:hidden; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "CLIP:rect(0, "ht_flex_format", auto, 0);" - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), - ht_flex(w-1-2*box_offset+2*clip_offset, tree->Parent->width, ht_get_fl_w(tree)), z ); } From 825b70511c07210d74816fe9a6565d0c56bf923e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 11:44:29 -0700 Subject: [PATCH 034/101] Make the chart widget responsive. Update page generation to produce responsive HTML and CSS. Add JS attributes to tell ChartJS how to resize the widget. --- centrallix-os/sys/js/htdrv_chart.js | 6 +++ centrallix/htmlgen/htdrv_chart.c | 68 ++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_chart.js b/centrallix-os/sys/js/htdrv_chart.js index 3b6d0abe3..87d7cb9ef 100755 --- a/centrallix-os/sys/js/htdrv_chart.js +++ b/centrallix-os/sys/js/htdrv_chart.js @@ -387,6 +387,12 @@ function cht_init(params) { this.update_soon = false; //see cht_object_available chart_wgt.ChartJsInit(); + + // Set ChartJS options. + const { options } = chart_wgt.chart; + options.responsive = false; + options.maintainAspectRatio = false; + options.resizeDelay = 10; } // Load indication diff --git a/centrallix/htmlgen/htdrv_chart.c b/centrallix/htmlgen/htdrv_chart.c index 073627a20..d421643cf 100644 --- a/centrallix/htmlgen/htdrv_chart.c +++ b/centrallix/htmlgen/htdrv_chart.c @@ -323,27 +323,54 @@ htchtScriptInclude(pHtSession session) void htchtGenHTML(pHtSession session, pWgtrNode tree, int z) { - char buf[32]; - - htchtGetCanvasId(tree, buf, sizeof(buf)); - - htrAddBodyItem_va(session,"
\n", - buf, - buf, - htchtGetWidth(tree), - htchtGetHeight(tree) + /** Get id. **/ + char id[32]; + htchtGetCanvasId(tree, id, sizeof(id)); + + /** Get layout data. **/ + const int x = htchtGetX(tree); + const int y = htchtGetY(tree); + const int w = htchtGetWidth(tree); + const int h = htchtGetHeight(tree); + + /** Write style rules for the container div. **/ + htrAddStylesheetItem_va(session, + "\t#%STR&SYMdiv { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z ); - - htrAddBodyItem(session,"

CHART HERE

\n"); - htrAddBodyItem(session,"
\n"); - - htrAddStylesheetItem_va(session, "\t#%STR&SYMdiv { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; } \n", - buf, - htchtGetX(tree), - htchtGetY(tree), - htchtGetWidth(tree), - htchtGetHeight(tree), - z + + /** Write the canvas HTML. **/ + /*** Israel: Dark magic and sorcery beyond my comprehension somehow + *** cause "CHART HERE" to render as the label for the chart. + ***/ + htrAddBodyItem_va(session, + "
" + "" + "

CHART HERE

" + "
" + "
", + id, + id, + w, + h ); } @@ -405,4 +432,3 @@ htchtInitialize() return 0; } - From cff62ebcb3ce0029cde6ce846e8e2bbe3b8701dc Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 11:46:20 -0700 Subject: [PATCH 035/101] Make the HTML widget responsive. Improve page generation methodology, leading to responsive pages. Improve C code formatting. --- centrallix-os/sys/js/htdrv_html.js | 2 + centrallix/htmlgen/htdrv_html.c | 119 ++++++++++++++++------------- 2 files changed, 68 insertions(+), 53 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_html.js b/centrallix-os/sys/js/htdrv_html.js index 3289d0b61..1300a79a3 100644 --- a/centrallix-os/sys/js/htdrv_html.js +++ b/centrallix-os/sys/js/htdrv_html.js @@ -300,6 +300,8 @@ function ht_init(param) //l.watch('source', ht_sourcechanged); pg_resize(l.parentLayer); + disableClippingCSS(l.parentLayer); + return l; } diff --git a/centrallix/htmlgen/htdrv_html.c b/centrallix/htmlgen/htdrv_html.c index 0b41d7b35..509438673 100644 --- a/centrallix/htmlgen/htdrv_html.c +++ b/centrallix/htmlgen/htdrv_html.c @@ -101,76 +101,52 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) { /** Only give x and y if supplied. **/ if (x < 0 || y < 0) - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(w,ht_get_total_w(tree),ht_get_fl_w(tree)),z+1); - } - else { htrAddStylesheetItem_va(s, - "\t#ht%POSpane { " - "POSITION:absolute; " + "\t#ht%POSpane, #ht%POSpane, #ht%POSfader { " + "POSITION:relative; " "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " "WIDTH:"ht_flex_format"; " "Z-INDEX:%POS; " "}\n", - id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + id, id, id, + ht_flex_w(w, tree), z ); + } + else + { htrAddStylesheetItem_va(s, - "\t#ht%POSpane2 { " + "\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { " "POSITION:absolute; " - "VISIBILITY:hidden; " + "VISIBILITY:inherit; " "LEFT:"ht_flex_format"; " "TOP:"ht_flex_format"; " "WIDTH:"ht_flex_format"; " "Z-INDEX:%POS; " "}\n", - id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), + id, id, id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), z ); - htrAddStylesheetItem_va(s, - "\t#ht%POSfader { " - "POSITION:absolute; " - "VISIBILITY:hidden; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " - "}\n", - id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - z+1 - ); } if (s->Capabilities.CSS1) { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { overflow:hidden; }\n",id); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { overflow:hidden; }\n",id); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { overflow:hidden; }\n",id); + htrAddStylesheetItem_va(s,"\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { overflow:hidden; }\n", id, id, id); htrAddStylesheetItem_va(s,"\t#ht%POSloader { overflow:hidden; visibility:hidden; position:absolute; top:0px; left:0px; width:0px; height:0px; }\n", id); } /** Write named global **/ htrAddWgtrObjLinkage_va(s, tree, "ht%POSpane",id); - htrAddScriptGlobal(s, "ht_fadeobj", "null", 0); - + htrAddScriptGlobal(s, "ht_fadeobj", "null", 0); + htrAddScriptInclude(s, "/sys/js/htdrv_html.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - + /** Event handler for click-on-link. **/ htrAddEventHandlerFunction(s, "document","CLICK","ht","ht_click"); htrAddEventHandlerFunction(s,"document","MOUSEOVER","ht","ht_mouseover"); @@ -178,16 +154,53 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) htrAddEventHandlerFunction(s,"document","MOUSEMOVE","ht", "ht_mousemove"); htrAddEventHandlerFunction(s,"document","MOUSEDOWN","ht", "ht_mousedown"); htrAddEventHandlerFunction(s,"document","MOUSEUP", "ht", "ht_mouseup"); - - /** Script initialization call. **/ - if (s->Capabilities.Dom0NS) - htrAddScriptInit_va(s," ht_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSpane2\"), faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSfader\"), source:\"%STR&JSSTR\", width:%INT, height:%INT, loader:null});\n", - name, name, id, name, id, - src, w,h); + + /** Script initialization call. **/ + if (s->Capabilities.Dom1HTML) + { + htrAddScriptInit_va(s, + "ht_init({" + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'ht%POSpane2'), " + "faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'ht%POSfader'), " + "source:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'ht%POSloader')" + "});\n", + name, + name, id, + name, id, + src, + w, + h, + name, id + ); + } + else if (s->Capabilities.Dom0NS) + { + htrAddScriptInit_va(s, + "ht_init({" + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'ht%POSpane2'), " + "faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), 'ht%POSfader'), " + "source:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "loader:null" + "});\n", + name, + name, id, + name, id, + src, + w, + h + ); + } else - htrAddScriptInit_va(s," ht_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSpane2\"), faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSfader\"), source:\"%STR&JSSTR\", width:%INT, height:%INT, loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), \"ht%POSloader\")});\n", - name, name, id, name, id, - src, w,h, name, id); + { + mssError(1, "HTHTML", "Browser not supported!!"); + } /** HTML body
element for the layer. **/ htrAddBodyItem_va(s,"
",id); @@ -214,14 +227,14 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) } /** If source is an objectsystem entry... **/ - if (src[0] && strncmp(src,"http:",5) && strncmp(src,"debug:",6)) + if (src[0] && strncmp(src, "http:", 5) != 0 && strncmp(src, "debug:", 6) != 0) { content_obj = objOpen(s->ObjSession,src,O_RDONLY,0600,"text/html"); if (content_obj) { - while((cnt = objRead(content_obj, sbuf, 159,0,0)) > 0) + while ((cnt = objRead(content_obj, sbuf, 159, 0, 0)) > 0) { - sbuf[cnt]=0; + sbuf[cnt] = 0; htrAddBodyItem(s, sbuf); } objClose(content_obj); From c7af7bbc33bb673871c29d790abab6f87689dc13 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:03:39 -0700 Subject: [PATCH 036/101] Make checkbox widget responsive. Update C to generate responsive CSS rules. Add an error for unexpected 'checked' values. --- centrallix/htmlgen/htdrv_checkbox.c | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/centrallix/htmlgen/htdrv_checkbox.c b/centrallix/htmlgen/htdrv_checkbox.c index c2f699e91..d5f164716 100644 --- a/centrallix/htmlgen/htdrv_checkbox.c +++ b/centrallix/htmlgen/htdrv_checkbox.c @@ -90,12 +90,29 @@ int htcbRender(pHtSession s, pWgtrNode tree, int z) { /** Write named global **/ htrAddWgtrObjLinkage_va(s, tree, "cb%INTmain", id); - - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#cb%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:13px; WIDTH:13px; Z-INDEX:%POS; }\n",id,x,y,z); + + /** Write style header. **/ + htrAddStylesheetItem_va(s, + "\t#cb%POSmain { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "height:13px; " + "width:13px; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + z + ); + + /** Include scripts. **/ htrAddScriptInclude(s,"/sys/js/htdrv_checkbox.js",0); htrAddScriptInclude(s,"/sys/js/ht_utils_hints.js",0); + /** Register event handlers. **/ htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "checkbox", "checkbox_mousedown"); htrAddEventHandlerFunction(s, "document","MOUSEUP", "checkbox", "checkbox_mouseup"); htrAddEventHandlerFunction(s, "document","MOUSEOVER", "checkbox", "checkbox_mouseover"); @@ -105,9 +122,9 @@ int htcbRender(pHtSession s, pWgtrNode tree, int z) { /** Script initialization call. **/ htrAddScriptInit_va(s," checkbox_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), fieldname:\"%STR&JSSTR\", checked:%INT, enabled:%INT, form:\"%STR&JSSTR\"});\n", name, fieldname,checked,enabled,form); - /** HTML body
element for the layers. **/ + /** Write HTML. **/ htrAddBodyItemLayerStart(s, 0, "cb%POSmain", id, NULL); - switch(checked) + switch (checked) { case 1: htrAddBodyItem_va(s," \n",!enabled); @@ -118,8 +135,10 @@ int htcbRender(pHtSession s, pWgtrNode tree, int z) { case -1: /* null */ htrAddBodyItem_va(s," \n",!enabled); break; + default: + fprintf(stderr, "Unexpected value %d for 'checked'.", checked); + break; } - htrAddBodyItemLayerEnd(s, 0); /** Check for more sub-widgets **/ From a5525b63cb083267c2402d64cb4297456e37b65c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:06:09 -0700 Subject: [PATCH 037/101] Update docs. Add docs for new tab functionality. Add docs for colstep_mode attribute on tables. Fix style issues in docs for tables. Clarify docs for label style. --- centrallix-doc/Widgets/widgets.xml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index 0ccb415b3..0dd4ddc37 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -2323,7 +2323,7 @@ MyButton "widget/imagebutton" The color (named or #numeric) of the text in the label when the user hovers the mouse over the label. - (e.g. bold). + 'bold' for bold text, 'italic' for italic text. The text that the label is to display. @@ -3481,7 +3481,7 @@ MyScrollPane "widget/scrollpane"

The tab pages are containers, and as such, controls of various kinds, including other tab controls, can be placed inside the tab pages.

-

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", with the exception of nonvisuals such as connectors.

+

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", except nonvisuals such as connectors.

Tab pages also have a 'visible' property which allows them to be hidden and revealed. This is used if the type is set to dynamic, but can be used manually as well.

@@ -3516,6 +3516,8 @@ MyScrollPane "widget/scrollpane" The location of the tabs: "top" (default), "bottom", "left", "right", or "none". The width of the tabs in pixels. This is optional for tab_locations of "top", "bottom", and "none". + + The height of the tabs in pixels. This is optional for all tab_locations. Defaults to 24px. The color of the text to be used on the tabs to identify them. @@ -3525,7 +3527,15 @@ MyScrollPane "widget/scrollpane" X-coordinate of the upper left corner of the tab control, relative to the container. - Y-coordinate of the upper left corner of the control, relative to its container. + 'client-side' or 'server-side'. This property is intended for developers (although it can give a very small performance boost). A value of "server-side" turns off JS rendering on the client. This does not work for dynamic width tabs (aka. top or bottom tabs with no 'tab_width' property). Defaults to "client-side". + + The amount to translate a selected tab along the side of the tab control. Defaults to 0px. + + The amount to translate a selected tab out and away from the side of the tab control. Defaults to 2px. + + The amount to translate a selected tab in the x direction. If set, overrides the value derived from select_translate_along and/or select_translate_out. + + The amount to translate a selected tab in the y direction. If set, overrides the value derived from select_translate_along and/or select_translate_out. @@ -3609,9 +3619,9 @@ myTabControl "widget/tab" -

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data.The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

+

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data. The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

- Table widgets come in three different flavors: static, dynamicpage, and dynamicrow.Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget.Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will.Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query.As of the time of writing of this document, only static mode and dynamicrow mode were supported. + Table widgets come in three different flavors: static, dynamicpage, and dynamicrow. Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget. Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will. Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query. As of the time of writing of this document, only static mode and dynamicrow mode were supported.

Table widgets allow the selection (keyboard, mouse, and data focus) of individual rows.

@@ -3636,6 +3646,8 @@ myTabControl "widget/tab" The vertical spacing between cells in the table, in pixels. Default is 1. The width of the column separation lines in pixels. Default is 1. + + Either 'full' or 'header'. Default is 'full'. Either "rows" (default) or "properties". In "properties" mode, the table displays one row per attribute, and so only displays the current record in the objectsource. In "rows" mode, the table displays one row per record in the objectsource. From 3669171a28778fa174450c92441df8db9837ca31 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:09:36 -0700 Subject: [PATCH 038/101] Add new content for testing. Add tab_features.app to show off the new tab features. Add tab_adventure.app to stress test using tabs for a text adventure. Add scrollpane_test.app to test the new scrollpane events and actions. Add several HTML examples for use when testing HTML. --- centrallix-os/samples/html_example1.html | 269 +++++++ centrallix-os/samples/html_example2.html | 232 ++++++ centrallix-os/samples/html_example2_long.html | 662 +++++++++++++++++ centrallix-os/samples/scrollpane_test.app | 262 +++++++ centrallix-os/samples/tab_adventure.app | 413 +++++++++++ centrallix-os/samples/tab_features.app | 679 ++++++++++++++++++ 6 files changed, 2517 insertions(+) create mode 100644 centrallix-os/samples/html_example1.html create mode 100644 centrallix-os/samples/html_example2.html create mode 100644 centrallix-os/samples/html_example2_long.html create mode 100644 centrallix-os/samples/scrollpane_test.app create mode 100644 centrallix-os/samples/tab_adventure.app create mode 100644 centrallix-os/samples/tab_features.app diff --git a/centrallix-os/samples/html_example1.html b/centrallix-os/samples/html_example1.html new file mode 100644 index 000000000..03d71043c --- /dev/null +++ b/centrallix-os/samples/html_example1.html @@ -0,0 +1,269 @@ + + + + + + + Simple Showcase — Modern HTML Example + + + + + +
+ + +
+
+

Features

+

+ Semantic layout, responsive CSS, accessible markup, and minimal JS. +

+ +
+
+
+ Semantic HTML +
header, main, + article, aside, footer
+
+
+ Responsive +
Grid and media + queries
+
+
+ Accessible +
ARIA roles, focus + states
+
+
+ Lightweight +
No frameworks, + small CSS
+
+
+
+ +
+

Interactive example

+

+ Click the button to toggle the sample panel in the sidebar. +

+ +
+
+ + +
+ +
+
Made with a focus on clarity and standards.
+
© Example Project
+
+
+ + + + + \ No newline at end of file diff --git a/centrallix-os/samples/html_example2.html b/centrallix-os/samples/html_example2.html new file mode 100644 index 000000000..7b09f7301 --- /dev/null +++ b/centrallix-os/samples/html_example2.html @@ -0,0 +1,232 @@ + + + + + + + ClaudCode — Quick Bird Brief + + + + + +
+
+ +
+

ClaudCode — Bird Brief

+
A compact, code-like summary about a bird in a red-blue theme. +
+
+
+ +
+
+
type: summary
+
species: Common + Kingfisher
+
+ +

Common Kingfisher (Alcedo atthis)

+

A small, vivid river bird known for rapid dives, bright plumage, and precise + hunting.

+ +
+Name: Common Kingfisher +Size: 16–17 cm +Habitat: Slow rivers, streams, ponds with perches +Diet: Small fish and aquatic insects +Behavior: Perches above water, hovers, and dives feet-first to catch prey +Breeding: Nest burrow in banks; clutches ~6 eggs +Notice: Iridescent blue upperparts, orange underparts, long bill +
+ +
+

Quick notes

+
    +
  • Territorial; often returns to regular perches.
  • +
  • Flight is rapid with short, direct wingbeats.
  • +
  • Indicator of healthy freshwater ecosystems.
  • +
+
+
+ + + +
Generated in a ClaudCode-like style • © Example
+
+ + + + + \ No newline at end of file diff --git a/centrallix-os/samples/html_example2_long.html b/centrallix-os/samples/html_example2_long.html new file mode 100644 index 000000000..f6c5a1cfc --- /dev/null +++ b/centrallix-os/samples/html_example2_long.html @@ -0,0 +1,662 @@ + + + + + + ClaudCode — Common Kingfisher (Alcedo atthis) — Extended Brief + + + + +
+
+ +
+

ClaudCode — Extended Kingfisher Brief

+
An expanded, accessible, and printable long-form profile of the Common Kingfisher.
+
+
+ +
+
+
format: extended-brief
+
species: Common Kingfisher
+
latin: Alcedo atthis
+
length: 16–17 cm
+
+ +

Common Kingfisher (Alcedo atthis)

+

A small, vividly coloured river bird noted for its electric blue upperparts, rich orange underparts, + and expert plunge-diving behaviour. This page collects natural history, identification tips, behaviour, + conservation notes, observation logs and a long FAQ designed to create a very long, scrollable document.

+ +
+Name: Common Kingfisher +Scientific name: Alcedo atthis +Length: 16–17 cm +Wingspan: 24–26 cm +Weight: ~34–45 g +Habitat: Slow rivers, streams, canals, ponds, and lakes with perches and clear water +Diet: Small fish, aquatic insects, crustaceans +Breeding: Burrow nests in soft banks, clutch size typically 5–7 eggs +Longevity: Usually 2–4 years in the wild; some individuals live longer +Status: Least Concern globally, but locally vulnerable to habitat loss +
+ +
+

Identification and field marks

+

Kingfishers are unmistakable when seen well: bright iridescent blue on their upperparts that flashes in sunlight, + warm orange on the underparts, a long straight black bill, compact body and short tail. Juveniles can appear duller + with some brown on the bill base. Males have all-black lower mandible while females often show an orange-red base + to the lower mandible.

+ +
+
+

Size & shape

+
    +
  • Short neck, large head for body size, long pointed bill adapted for catching fish.
  • +
  • Wings: Short and rounded; flight is direct and fast.
  • +
  • Perching posture: Upright and still, often at water edge or on low branches.
  • +
+
+
+

Plumage details

+
    +
  • Dorsal surface: Iridescent turquoise to deep blue depending on light and feather structure.
  • +
  • Ventral surface: Warm orange/rust; throat white in many subspecies.
  • +
  • Distinctive white patch on neck and sides of neck in some populations.
  • +
+
+
+
+ +
+

Behaviour and hunting

+

Common Kingfishers hunt by sitting on a low perch above the water, watching for fish. When prey is spotted they + hover briefly (in some instances) and then dive feet-first to seize the fish with their bill. Prey is usually killed + by a sharp blow or by being swallowed head-first to ease passage.

+ +

They are territorial; one pair defends a stretch of river or pond and can be aggressive toward intruders. Outside the + breeding season, territories may still be defended but can overlap at high-quality feeding sites.

+ +

Daily rhythm

+
    +
  1. Early morning: Active feeding after dawn; anglers and birders often see highest activity.
  2. +
  3. Midday: Periods of rest on perches or in sheltered vegetation.
  4. +
  5. Dusk: Increased activity before nightfall in some habitats.
  6. +
+ +

Vocalisations

+

The typical call is a high, sharp 'chee' or 'tsee' given in flight or when alarmed. Calls are short and direct and + can be useful to locate them when dense vegetation hides visual detection.

+
+ +
+

Diet and feeding strategy

+

Diet is dominated by small fish (minnows, sticklebacks, small cyprinids) and aquatic invertebrates. They adjust + their prey selection to the local availability: ponds may yield dragonfly larvae and small crustaceans while + rivers may produce fish-rich foraging.

+ + + + + + + + + + +
Prey typeTypical sizeNotes
Small fish2–6 cmMain food in fish-rich streams
Aquatic insectsvariableImportant in ponds and marsh edges
CrustaceanssmallOccasionally taken in brackish or inland waters
+ +

Feeding technique: Spot, hover or plunge; retrieve to perch; orient prey head-first for swallowing.

+
+ +
+

Breeding and life cycle

+

Breeding pairs excavate burrows in soft clay or earth banks. The burrow commonly slopes upward to a chamber where eggs + are laid. Clutch size is typically 5–7 eggs. Both adults feed chicks. Fledging occurs after several weeks; juveniles may + remain in the natal territory for some time while learning to fish.

+ +

Timeline (typical temperate population)

+
    +
  1. Pair formation and nest-site selection: late winter to early spring.
  2. +
  3. Egg laying: spring (timing depends on latitude and climate).
  4. +
  5. Incubation: ~19–21 days.
  6. +
  7. Fledging: 23–27 days after hatching (approximate).
  8. +
+ +

Note: Some populations have multiple broods if conditions allow.

+
+ +
+

Distribution and habitat

+

Range: Widespread across Europe, Asia and parts of North Africa. Absent from high alpine zones and extensive deserts. + The species is sedentary in many parts of its range but undertakes local movements linked to ice cover and food availability.

+ +
+
+

Preferred habitat

+
    +
  • Slow-moving rivers with clear water and abundant small fish.
  • +
  • Ponds, lakes and canals with emergent vegetation and low perches.
  • +
  • Sheltered banks with soft substrate for burrow excavation.
  • +
+
+
+

Threats to habitat

+
    +
  • Water pollution reducing prey availability.
  • +
  • Bank engineering removing nesting sites.
  • +
  • Human disturbance near nesting banks and feeding areas.
  • +
+
+
+
+ +
+

Conservation notes

+

Globally the Common Kingfisher is currently assigned Least Concern, but local declines occur where water quality + deteriorates or banks are heavily modified. Conservation actions that help kingfishers include riparian buffer zones, + protection of nesting banks, and restoration of slow-flowing, fish-rich waters.

+ +

Practical measures

+
    +
  • Maintain and restore soft riverbanks or provide artificial nesting banks where appropriate.
  • +
  • Limit pesticide and nutrient runoff to maintain aquatic invertebrate and fish populations.
  • +
  • Encourage low-impact access for anglers and birdwatchers to reduce nest disturbance.
  • +
+
+ +
+

Observation logs & long notes (sample entries)

+ +

The following sample logs present extended, realistic observation entries to increase page length and + provide practical examples for citizen scientists. Repeated style is intentional to produce a long scrollable page.

+ +
+
+

Log entry: Riverbank surveillance — 2025-04-03

+

07:20 — Arrived at eastern bend. Water level low after recent dry spell. Observed single adult kingfisher + perched on willow stump at 10m from bank. Perched duration approx. 3 minutes; then dive, successful capture of + 3 cm fish. Called once in flight. Weather: still, pale sun. Notes: fish abundance low but still present near margin.

+
+ +
+

Log entry: Pond edge — 2024-08-11

+

16:30 — Two adults observed; one juvenile present (fuzzier plumage). Juvenile attempted several dives, missed on + first two attempts, succeeded on third. Adults delivered prey to juvenile at perch. Notes: ideal demonstration of + parental provisioning behaviour. Weather: warm, light breeze.

+
+ +
+

Log entry: Canal survey — 2023-11-22

+

09:00 — Single individual flying low along canal, direct flight, short perches. Habitat: concrete-lined banks but + with some emergent vegetation pockets. Observed call frequently when flushed by passing cyclist. Good example of + adaptability to semi-urban watercourses where sufficient prey exists.

+
+
+ + +
+

Extended note: The common kingfisher is often used as an indicator species for freshwater health. + Its presence signals adequate food webs and structural habitat. Because it hunts by sight, water clarity and prey + visibility directly impact foraging success. Where rivers are turbid or chemically altered, kingfisher abundance + typically falls.

+ +

Extended note (continued): When documenting observations, use standardized forms: date, time, + weather, number of individuals, behaviour, and any notes on prey or nesting. Use GPS coordinates or precise map + references for long-term monitoring projects. Photographs are valuable but avoid getting too close to active nests.

+ +

Extended note (continued): Citizen science platforms often aggregate dozens to thousands of + entries per site. Longitudinal records can reveal subtle trends—seasonal shifts in breeding times, range expansions, + or population declines tied to pollution events. Always record effort (time spent, area covered) to allow interpretation + of sighting frequency.

+
+
+ +
+ + + +

Images are placeholders in this example. Replace with high-resolution, + credited photographs for publication or print use. Ensure any images of nests comply with disturbance-avoidance guidelines.

+
+ +
+

Frequently asked questions — extended

+ +
+ Q: Where can I reliably see kingfishers? +
+

A: Look for clear, slow-flowing waters with low overhanging perches. Early morning and late + afternoon are best. Ponds and canals with good prey abundance also hold kingfishers. Avoid approaching nests or + getting between adults and their chicks.

+
+
+ +
+ Q: How can I help kingfisher conservation? +
+

A: Reduce nutrient and pesticide runoff, support riparian restoration, and protect nesting + banks by excluding heavy machinery from sensitive sites during breeding season. Build local awareness among + anglers and landowners about the species' needs.

+
+
+ +
+ Q: What is the difference between the common kingfisher and similar species? +
+

A: The common kingfisher is smaller than some other kingfisher species, and has a bright orange + underside and turquoise upperparts. Regional confusion can occur with local kingfisher species; focus on size, + bill colour patterns, and habitat to differentiate.

+
+
+ + +
+ Q: Can kingfishers live in urban areas? +
+

A: Yes, provided there are fish-rich waters and suitable perches. Urban canals and parks + with good water quality can host kingfishers. Noise and disturbance may reduce nesting success in some settings.

+
+
+ +
+ Q: Are kingfishers migratory? +
+

A: Many populations are largely resident, but some undertake local or seasonal movements, + especially where winter ice covers feeding waters. Subspecies in colder regions may move southward in severe winters.

+
+
+ +
+ Q: Why is water clarity important? +
+

A: Kingfishers rely on sight to detect and accurately time dives for prey. Turbidity reduces + foraging efficiency and can force birds to move to clearer waters or reduce breeding success due to insufficient food.

+
+
+
+ +
+

Glossary — long list of relevant terms

+
    +
  • Burrow nest — a nesting tunnel excavated into a bank or soft soil.
  • +
  • Clutch — a set of eggs laid in one nesting attempt.
  • +
  • Fledge — the stage when nestlings first fly.
  • +
  • Iridescence — structural coloration causing shifting blue/green sheen.
  • +
  • Riparian — relating to riverbanks and associated ecosystems.
  • +
  • Turbidity — cloudiness of water due to suspended particles.
  • +
  • Provisioning — adults bringing food to chicks.
  • +
  • Territoriality — defence of a specific area against conspecifics.
  • +
  • Plunge-dive — hunting technique involving a sudden dive into water.
  • +
  • Perch-hunting — hunting from a fixed vantage point.
  • +
  • Foraging — searching for food.
  • +
  • Subspecies — geographically distinct population with minor morphological differences.
  • +
+ + +

Further reading: consult regional field guides for subspecies details and local + conservation status. Many local bird clubs publish distribution atlases that include long-term records for monitoring.

+ +

Additional note: When compiling data from multiple sources, record the source, date and any + observational caveats to ensure checkable datasets. Metadata are as important as the sighting itself for long-term value.

+
+ +
+

Printable factsheet (compact)

+
+Common name: Common Kingfisher +Scientific name: Alcedo atthis +Length: 16–17 cm +Habitat: Rivers, streams, ponds with clear water +Diet: Small fish and aquatic invertebrates +Breeding: Burrow in banks; 5–7 eggs per clutch +Conservation: Protect water quality and nesting banks +
+ +

Use the above block for quick prints: set printer margins to narrow and choose a dark background-friendly + print stylesheet if required. For distribution, include contact and credit information on the printed sheet.

+
+ +
+

Extended commentary — natural history essays

+ +

Essay 1: The kingfisher's role in folklore and culture. Across many regions, kingfishers have been associated + with calm seas, good luck for fishermen, and precision. Their vivid colours have inspired artists and poets who draw + metaphors between the bird's flash of blue and moments of sudden insight or luck.

+ +

Essay 2: Morphology and hydrodynamics of the dive. The kingfisher's skull and bill shape reduce splash + and improve hydrodynamic entry. Researchers have studied feather microstructure and bill angle to understand how the bird + minimizes impact and maximizes capture success.

+ +

Essay 3: Long-term monitoring case study. A multi-decade dataset from a temperate river shows correlation + between kingfisher abundance and multi-year trends in nutrient loads and fish community composition. Restoration improved + sightings within five years, demonstrating recovery potential when water quality is addressed.

+
+ + +
+

Further resources and reading list

+
    +
  1. Regional bird atlases and national field guides.
  2. +
  3. Peer-reviewed studies on kingfisher foraging and nesting ecology.
  4. +
  5. Conservation NGO reports on freshwater habitat restoration.
  6. +
  7. Citizen science portals aggregating observation records.
  8. +
  9. Practical guides for building artificial nest banks (where appropriate).
  10. +
  11. Water quality monitoring manuals and citizen protocols.
  12. +
+ +

Acknowledgement: This document synthesizes publicly available knowledge and best-practice conservation advice. + Replace example logs and images with verified, local data for project-specific outputs.

+ +

Append: repeated paragraph to increase document length and provide more scrollable content. The common kingfisher + remains one of the most photogenic and studied small waterbirds across its range. Continued engagement from birdwatchers, + anglers, scientists and land managers will help ensure its persistence in managed and natural waters alike.

+ +

Appendix: A final long paragraph describing observational protocol. Before approaching sensitive sites, + assess visibility from a distance, use binoculars or a scope where possible, and keep movements slow and quiet. Avoid walking + up or down nesting banks when active, and report breeding sites to local conservation authorities if protection is needed.

+
+
+ + + +
Generated in a ClaudCode-like style • © Example • Last updated: 2026-01-13
+
+ + + + \ No newline at end of file diff --git a/centrallix-os/samples/scrollpane_test.app b/centrallix-os/samples/scrollpane_test.app new file mode 100644 index 000000000..31db45c6d --- /dev/null +++ b/centrallix-os/samples/scrollpane_test.app @@ -0,0 +1,262 @@ +$Version=2$ +test "widget/page" + { + title = "Test App"; + bgcolor = "#ffffff"; + + x = 0; y = 0; + width = 500; height = 500; + + // Description: + // This page is intended for testing scroll pane functionality, including + // the associated events, actions, etc. It specifically tests the scroll + // pane when used on an HTML widget, allowing us to load long HTML pages + // to test scrolling large amounts of content. + + pane "widget/pane" + { + x = 10; y = 10; + width = 480; height = 480; + + scroll "widget/scrollpane" + { + x = 0; y = 20; + width = 300; height = 300; + bgcolor = "#c0f1ba"; + + // Content. + // html "widget/html" + // { + // x = 0; y = 0; + // width = 300; height = 300; + + // mode = "dynamic"; + // source = "/samples/html_example2_long.html"; + // } + + // Vertical content. + spacer "widget/pane" + { + x = 0; y = 0; + width = 0; height = 2000; + fl_height = 0; + style = flat; + } + a1 "widget/pane" + { + x = 10; y = 0; + width = 250; height = 500; + + bgcolor = "#e6b2c4"; + } + a2 "widget/pane" + { + x = 20; y = 500; + width = 200; height = 500; + + bgcolor = "#e6d2c4"; + } + a3 "widget/pane" + { + x = 30; y = 1000; + width = 150; height = 500; + + bgcolor = "#e6b2c4"; + } + a4 "widget/pane" + { + x = 40; y = 1500; + width = 100; height = 500; + + bgcolor = "#e6d2c4"; + } + + // Horizontal Content. + // Unused because there is no horizontal scrollpane. + // spacer "widget/pane" + // { + // x = 0; y = 0; + // width = 2000; height = 0; + // fl_height = 0; + // style = flat; + // } + // a1 "widget/pane" + // { + // x = 0; y = 10; + // width = 500; height = 250; + + // bgcolor = "#e6b2c4"; + // } + // a2 "widget/pane" + // { + // x = 500; y = 20; + // width = 500; height = 200; + + // bgcolor = "#e6d2c4"; + // } + // a3 "widget/pane" + // { + // x = 1000; y = 30; + // width = 500; height = 150; + + // bgcolor = "#e6b2c4"; + // } + // a4 "widget/pane" + // { + // x = 1500; y = 40; + // width = 500; height = 100; + + // bgcolor = "#e6d2c4"; + // } + + // Ads, testing the Scroll event. + adv "widget/variable" { type = integer; value = runclient(0); } + ad1v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad1c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad1v:value == 0 and :Percent > 30 and :Change > 0); + Message = runclient("Advertisement!!"); + } + ad1d "widget/connector" + { + event = Scroll; + target = ad1v; + action = SetValue; + event_condition = runclient(:ad1v:value == 0 and :Percent > 30 and :Change > 0); + Value = runclient(1); + } + + ad2v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad2c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad2v:value == 0 and :Percent > 40 and :Change > 0); + Message = runclient("Advertisement 2!!"); + } + ad2d "widget/connector" + { + event = Scroll; + target = ad2v; + action = SetValue; + event_condition = runclient(:ad2v:value == 0 and :Percent > 40 and :Change > 0); + Value = runclient(1); + } + + ad3v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad3c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad3v:value == 0 and :Percent > 50 and :Change > 0); + Message = runclient("Advertisement 3!!"); + } + ad3d "widget/connector" + { + event = Scroll; + target = ad3v; + action = SetValue; + event_condition = runclient(:ad3v:value == 0 and :Percent > 50 and :Change > 0); + Value = runclient(1); + } + + // Log events for debugging. + debug_Scroll "widget/connector" + { + event = Scroll; + target = test; + action = Log; + Message = runclient("Scroll " + :Percent + " " + :Change); + } + debug_Wheel "widget/connector" + { + event = Wheel; + target = test; + action = Log; + Message = runclient("Wheel: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_Click "widget/connector" + { + event = Click; + target = test; + action = Log; + Message = runclient("Click: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseDown "widget/connector" + { + event = MouseDown; + target = test; + action = Log; + Message = runclient("MouseDown: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseUp "widget/connector" + { + event = MouseUp; + target = test; + action = Log; + Message = runclient("MouseUp: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseOver "widget/connector" + { + event = MouseOver; + target = test; + action = Log; + Message = runclient("MouseOver"); + } + debug_MouseOut "widget/connector" + { + event = MouseOut; + target = test; + action = Log; + Message = runclient("MouseOut"); + } + debug_MouseMove "widget/connector" + { + event = MouseMove; + target = test; + action = Log; + Message = runclient("MouseMove"); + } + } + + button1 "widget/textbutton" + { + x = 5; y = 5; + width = 75; height = 30; + font_size = 18; + + bgcolor="#0c0447"; + text = "To 45%"; + + button1c "widget/connector" + { + event = Click; + target = scroll; + action = ScrollTo; + Percent = 45; + } + } + button2 "widget/textbutton" + { + x = 5; y = 40; + width = 75; height = 30; + font_size = 18; + + bgcolor="#241672"; + text = "To 100px"; + + button2c "widget/connector" + { + event = Click; + target = scroll; + action = ScrollTo; + Offset = 100; + } + } + } + } \ No newline at end of file diff --git a/centrallix-os/samples/tab_adventure.app b/centrallix-os/samples/tab_adventure.app new file mode 100644 index 000000000..a05481949 --- /dev/null +++ b/centrallix-os/samples/tab_adventure.app @@ -0,0 +1,413 @@ +$Version=2$ +FourTabs "widget/page" { + title = "Tab Adventure"; + bgcolor = "#c0c0c0"; + x=0; y=0; width=250; height=300; + + MainTab "widget/tab" { + x = 0; y = 0; width=250; height=300; + tab_location = none; + + bgcolor = "#05091dff"; + inactive_bgcolor = "#01010aff"; + selected_index = 1; + + Scene1 "widget/tabpage" { + height=300; + fl_width = 100; + + Scene1Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="The Beginning"; } + Scene1Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You wake up surounded by the ruins of a village. Houses are burned, walls are colapsed, and roves are caved in. + The house you're 'in' doesn't even have a roof, and barely half a wall is still standing. + "; + } + Scene1Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene1Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Get up and investigate the village"; + Scene1Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + + Scene1Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Look around the 'house'"; + Scene1Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene2 "widget/tabpage" { + height=300; + + Scene2Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Exploring"; } + Scene2Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You get up and begin to walk around the village. Everything is in pretty bad shape, but a lot of brick fireplaces + were able to survive decently well. + "; + } + Scene2Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene2Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Leave the village"; + Scene2Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=6; } + } + + Scene2Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Investigate a fireplace"; + Scene2Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=7; } + } + + Scene2Option3 "widget/textbutton" { + x = 130; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Go back to the first house where you woke up."; + Scene2Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene3 "widget/tabpage" { + height=300; + + Scene3Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Look around the house"; } + Scene3Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You look around the house where you woke up. + "; + } + Scene3Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text=""; } + + Scene3Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "..."; + Scene3Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=4; } + } + } + + Scene4 "widget/tabpage" { + height=300; + + Scene4Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Food!"; } + Scene4Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You find some scraps of food and realize that you're famished! + The food looks pretty old, though. Maybe it's not a good idea... + "; + } + Scene4Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene4Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Eat the food"; + Scene4Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=5; } + } + + Scene4Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Leave it and check out the village"; + Scene4Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + } + + Scene5 "widget/tabpage" { + height=300; + + Scene5Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Bad food..."; } + Scene5Text1 "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text="You eat the food."; + } + Scene5Text2 "widget/label" { + x=10; y=90; width=250; height=80; + font_size=18; fgcolor="#da2d2dff"; + text="OH NO!!"; + } + Scene5Text3 "widget/label" { + x=10; y=120; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text="You should not have done that. You feel very sick."; + } + Scene5Ask "widget/label" { x=10; y=150; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text=". . ."; } + + Scene5Option1 "widget/textbutton" { + x = 10; y = 190; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = ". . ."; + Scene5Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene6 "widget/tabpage" { + height=300; + + Scene6Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="FREEDOM"; } + Scene6Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You leave the village. As you get further away from the depressing place, you begin to run. + You sprint across planes and through vallies, never looking back or missing the acursed ruins you left behind. + After a while, though, you suddenly fall off some kind of edge and plumit until everything goes black... + "; + } + + Scene6Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#1a1066ff"; + text = "WHAT?! The writer made the world THAT small?"; + Scene6Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene7 "widget/tabpage" { + height=300; + + Scene7Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Fireplaces"; } + Scene7Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + There's several firepalces around, so you have a few options. + "; + } + + Scene7Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Sturdy Fireplace"; + Scene7Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=8; } + } + + Scene7Option2 "widget/textbutton" { + x = 10; y = 150; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Leaning Fireplace"; + Scene7Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=10; } + } + + Scene7Option3 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Crumbling Fireplace"; + Scene7Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=9; } + } + } + + Scene8 "widget/tabpage" { + height=300; + + Scene8Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="'Sturdy' Fireplace"; } + Scene8Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You approach the sturdy fireplace and begin to investigate it. Then, you find a loose brick! + However, as you slowly pull it out, the entire fireplace suddenly topples over onto you. + Guess it wasn't so sturdy after all!! + "; + } + + Scene8Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#660009ff"; + text = "The end"; + Scene8Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene9 "widget/tabpage" { + height=300; + + Scene9Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Crumbling Fireplace"; } + Scene9Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You approach the crumbling fireplace and realize it's the one in the house where you woke up! + "; + } + + Scene9Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Neat!"; + Scene9Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene10 "widget/tabpage" { + height=300; + + Scene10Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Leaning Fireplace"; } + Scene10Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + As you investigate the leaning fireplace, you find a loose brick. You carefully slide + the brick out, and behind it you pull out a golden scroll that seems to call to you. + "; + } + Scene10Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene10Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Read it"; + Scene10Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=11; } + } + + Scene10Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Yikes! Probably cursed. Put it down."; + Scene10Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + } + + Scene11 "widget/tabpage" { + height=300; + + Scene11Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Leaning Fireplace: Last Chance"; } + Scene11Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You know, the scroll probably is cursed. + Reading it might not be a good idea! + "; + } + Scene11Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene11Option7 "widget/textbutton" { + x = 82; y = 230; width = 25; height = 15; font_size=6; fgcolor="#fcc885"; bgcolor="#580251"; + text = "Read it anyway!"; + Scene11Option7C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=12; } + } + + Scene11Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll go investigate the village"; + Scene11Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + + Scene11Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll look around the house where I woke up."; + Scene11Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + + Scene11Option3 "widget/textbutton" { + x = 130; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll go to the sturdy fireplace."; + Scene11Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=8; } + } + + Scene11Option4 "widget/textbutton" { + x = 10; y = 220; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll leave the village."; + Scene11Option4C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=6; } + } + + Scene11Option5 "widget/textbutton" { + x = 70; y = 220; width = 50; height = 25; font_size=18; bgcolor="#1f0757bb"; + text = "Ok, knock myself out."; + Scene11Option5C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=1; } + } + + Scene11Option6 "widget/textbutton" { + x = 130; y = 220; width = 50; height = 25; font_size=18; bgcolor="#3e0655ff"; + text = "Ok, I'll die."; + Scene11Option6C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene12 "widget/tabpage" { + height=300; + + Scene12Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="You really shouldn't read that!"; } + Scene12Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + What? How did you do that! You're not supposed to do that! + "; + } + + Scene12Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "JUST LET ME READ THE SCROLL!!!"; + Scene12Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=13; } + } + } + + + Scene13 "widget/tabpage" { + height=300; + + Scene13Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="VICTORY"; } + Scene13Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + Ok... fine. You found the golden scroll and won the game. + Look, I had to make it at least a little bit difficult for you! + "; + } + + Scene13Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Yay!"; + Scene13Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=14; } + } + } + + + Scene14 "widget/tabpage" { + height=300; + + Scene14Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="The End"; } + Scene14Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + Ok... fine. You found the golden scroll and won the game. + Just thought I'd see if I could trick you there hahahah. + "; + } + + Scene14Text2 "widget/label" { + x=10; y=120; width=250; height=80; + font_size=18; fgcolor="#d9e97dff"; + text=" + Thank you for playing! + "; + } + } + + Scene15 "widget/tabpage" { + height=300; + + Scene15Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#ff001c"; text="DEATH"; } + Scene15Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You died... Let's call this a learning experience. + Better luck next time. + "; + } + Scene15Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene15Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Try again?"; + Scene15Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=1; } + } + + Scene15Option2 "widget/textbutton" { x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#470404ff"; text = "Give up"; } + } + } +} diff --git a/centrallix-os/samples/tab_features.app b/centrallix-os/samples/tab_features.app new file mode 100644 index 000000000..bcde25340 --- /dev/null +++ b/centrallix-os/samples/tab_features.app @@ -0,0 +1,679 @@ +$Version=2$ +TabFeatures "widget/page" + { + x = 0; y = 0; + width = 500; height = 500; + + title = "Tab Features Demonstrated"; + bgcolor = "#b0b0b0"; + + tloc "widget/parameter" { type = "string"; default = "top"; } + w "widget/parameter" { type = "integer"; default = 220; } + h "widget/parameter" { type = "integer"; default = 70; } + + tloc_label "widget/label" + { + x = 2; y = 1; + width = 50; height = 20; + font_size = 16; + + text = "Tab Location:"; + } + + button_top "widget/textbutton" + { + x = 55; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Top"; + bgcolor="#0c0447ff"; + + button_top_c "widget/connector" + { + event = Click; + target = TabFeatures; + action = LoadPage; + Source = "TabFeatures.app?tloc=top&w=220&h=70"; + } + } + + button_bottom "widget/textbutton" + { + x = 85; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Bottom"; + bgcolor="#0c0447ff"; + + button_bottom_c "widget/connector" + { + event = Click; + target = TabFeatures; + action = LoadPage; + Source = "TabFeatures.app?tloc=bottom&w=220&h=70"; + } + } + + button_left "widget/textbutton" + { + x = 115; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Left"; + bgcolor="#0c0447ff"; + + button_left_c "widget/connector" + { + event = Click; + target = TabFeatures; + action = LoadPage; + Source = "TabFeatures.app?tloc=left&w=140&h=90"; + } + } + + button_right "widget/textbutton" + { + x = 145; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Right"; + bgcolor="#0c0447ff"; + + button_right_c "widget/connector" + { + event = Click; + target = TabFeatures; + action = LoadPage; + Source = "TabFeatures.app?tloc=right&w=140&h=90"; + } + } + + t1 "widget/tab" + { + x = 0; y = 20; + height = 500; width = 500; + + bgcolor = "#c0c0c0"; + inactive_bgcolor = "#a8a8a8"; + selected_index = 2; + tab_location = top; + select_translate_out = 1; + + t11 "widget/tabpage" + { + title = "Spacing Rant"; + + t11l0 "widget/label" { x=20; y=20; width=200; height=40; font_size=24; text="Spacing Rant"; } + + t11t1 "widget/tab" + { + x = 20; y = 60; + width = runserver(:this:w); height = 100; + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; // "/sys/images/4Color.png" + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t11t11 "widget/tabpage" { title = "Tab 1"; t11t11l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } } + t11t12 "widget/tabpage" { title = "Tab 2"; t11t12l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } } + t11t13 "widget/tabpage" { title = "Tab 3"; t11t13l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } } + t11t14 "widget/tabpage" { title = "Tab 4"; t11t14l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } } + + t11t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t11t2; + TabIndex = runclient(:t11t1:selected_index); + } + } + + t11t2 "widget/tab" + { + x = 260; y = 60; + width = runserver(:this:w); height = 100; + tab_location = runserver(:this:tloc); + tab_width = 80; + + bgcolor = "#c0c0c0"; + inactive_bgcolor = "#b8b8b8"; + selected = t11t22; + + t11t21 "widget/tabpage" { title = "Tab 1"; t11t21l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } } + t11t22 "widget/tabpage" { title = "Tab 2"; t11t22l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } } + t11t23 "widget/tabpage" { title = "Tab 3"; t11t23l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } } + t11t24 "widget/tabpage" { title = "Tab 4"; t11t24l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } } + + t11t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t11t1; + TabIndex = runclient(:t11t2:selected_index); + } + } + + t11p "widget/pane" + { + x = 20; y = 180; width=runserver(:this:w); height=100; + + t11p1l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } + t11p2l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } + t11p3l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } + t11p4l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } + } + } + + t12 "widget/tabpage" + { + title = "Tab Spacing"; + + t12l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Tab Spacing"; } + + t12l1 "widget/label" { x=30; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Default (2px)"; } + t12t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t12t11 "widget/tabpage" { title = "First"; t12t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t12 "widget/tabpage" { title = "Looong Tab"; t12t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t13 "widget/tabpage" { title = "S"; t12t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t14 "widget/tabpage" { title = "Last Tab"; t12t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t2; + TabIndex = runclient(:t12t1:selected_index); + } + } + + t12l2 "widget/label" { x=270; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="No Tab Spacing"; } + t12t2 "widget/tab" + { + x = 260; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t22; + tab_spacing = 0; + + t12t21 "widget/tabpage" { title = "First"; t12t21l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t22 "widget/tabpage" { title = "Looong Tab"; t12t22l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t23 "widget/tabpage" { title = "S"; t12t23l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t24 "widget/tabpage" { title = "Last Tab"; t12t24l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t3; + TabIndex = runclient(:t12t2:selected_index); + } + } + + t12l3 "widget/label" { x=30; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Spacing 8px"; } + t12t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_spacing = 8; + + t12t31 "widget/tabpage" { title = "First"; t12t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t32 "widget/tabpage" { title = "Looong Tab"; t12t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t33 "widget/tabpage" { title = "S"; t12t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t34 "widget/tabpage" { title = "Last Tab"; t12t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t4; + TabIndex = runclient(:t12t3:selected_index); + } + } + + t12l4 "widget/label" { x=270; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Spacing 16px"; } + t12t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t42; + tab_spacing = 16; + + t12t41 "widget/tabpage" { title = "First"; t12t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t42 "widget/tabpage" { title = "Looong Tab"; t12t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t43 "widget/tabpage" { title = "S"; t12t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t44 "widget/tabpage" { title = "Last Tab"; t12t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t5; + TabIndex = runclient(:t12t4:selected_index); + } + } + + t12l5 "widget/label" { x=30; y=355; width=60; height=20; fl_width = 1; font_size=16; style="bold"; text="Tab Spacing -8px"; } + t12l5a "widget/label" { x=90; y=357; width=runserver(:this:w-80); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t12t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_spacing = -8; + + t12t51 "widget/tabpage" { title = "First"; t12t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t52 "widget/tabpage" { title = "Looong Tab"; t12t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t53 "widget/tabpage" { title = "S"; t12t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t54 "widget/tabpage" { title = "Last Tab"; t12t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t6; + TabIndex = runclient(:t12t5:selected_index); + } + } + + t12l6 "widget/label" { x=270; y=355; width=70; height=20; fl_width = 1; font_size=16; style="bold"; text="Tab Spacing -16px"; } + t12l6a "widget/label" { x=345; y=357; width=runserver(:this:w-70); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t12t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t62; + tab_spacing = -16; + + t12t61 "widget/tabpage" { title = "First"; t12t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t62 "widget/tabpage" { title = "Looong Tab"; t12t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t63 "widget/tabpage" { title = "S"; t12t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t64 "widget/tabpage" { title = "Last Tab"; t12t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t2; + TabIndex = runclient(:t12t6:selected_index); + } + } + } + + t13 "widget/tabpage" + { + title = "Tab Height"; + + t13l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Tab Height"; } + + t13l1 "widget/label" { x=30; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Default (24px)"; } + t13t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t13t11 "widget/tabpage" { title = "First"; t13t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t12 "widget/tabpage" { title = "Looong Tab"; t13t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t13 "widget/tabpage" { title = "S"; t13t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t14 "widget/tabpage" { title = "Last Tab"; t13t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t3; + TabIndex = runclient(:t13t1:selected_index); + } + } + + t13l2 "widget/label" { x=270; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="No Tab Height"; } + t13l2a "widget/label" + { + x = 270; y = 110; + width = runserver(:this:w-20); height = runserver(:this:h - 20); + font_size = 24; style = bold; fgcolor = "red"; + text = "Not allowed"; + } + + t13l2b "widget/label" + { + x = 270; y = 140; + width = runserver(:this:w-20); height = 30; + font_size = 18; style = italic; fgcolor = "red"; + text = "Because I hate fun."; + } + + t13l3 "widget/label" { x=30; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Height 32px"; } + t13t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_height = 32; + + t13t31 "widget/tabpage" { title = "First"; t13t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t32 "widget/tabpage" { title = "Looong Tab"; t13t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t33 "widget/tabpage" { title = "S"; t13t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t34 "widget/tabpage" { title = "Last Tab"; t13t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t4; + TabIndex = runclient(:t13t3:selected_index); + } + } + + t13l4 "widget/label" { x=270; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Height 48px"; } + t13t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t13t42; + tab_height = 48; + + t13t41 "widget/tabpage" { title = "First"; t13t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t13t42 "widget/tabpage" { title = "Looong Tab"; t13t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t13t43 "widget/tabpage" { title = "S"; t13t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t13t44 "widget/tabpage" { title = "Last Tab"; t13t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t13t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t5; + TabIndex = runclient(:t13t4:selected_index); + } + } + + t13l5 "widget/label" { x=30; y=355; width=60; height=20; font_size=16; style="bold"; text="Tab Height 16px"; } + t13l5a "widget/label" { x=90; y=357; width=runserver(:this:w-80); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not supported)"; } + t13t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_height = 16; + + t13t51 "widget/tabpage" { title = "First"; t13t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t52 "widget/tabpage" { title = "Looong Tab"; t13t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t53 "widget/tabpage" { title = "S"; t13t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t54 "widget/tabpage" { title = "Last Tab"; t13t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t6; + TabIndex = runclient(:t13t5:selected_index); + } + } + + t13l6 "widget/label" { x=270; y=355; width=60; height=20; font_size=16; style="bold"; text="Tab Spacing 8px"; } + t13l6a "widget/label" { x=335; y=357; width=runserver(:this:w-80); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not supported)"; } + t13t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t13t62; + tab_height = 8; + + t13t61 "widget/tabpage" { title = "First"; t13t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t13t62 "widget/tabpage" { title = "Looong Tab"; t13t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t13t63 "widget/tabpage" { title = "S"; t13t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t13t64 "widget/tabpage" { title = "Last Tab"; t13t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t13t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t1; + TabIndex = runclient(:t13t6:selected_index); + } + } + } + + + t14 "widget/tabpage" + { + title = "Selection Offsets"; + + t14l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Selection Offsets"; } + + t14l1 "widget/label" { x=30; y=75; width=220; height=40; font_size=16; style="bold"; text="Default"; } + t14t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t14t11 "widget/tabpage" { title = "First"; t14t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t12 "widget/tabpage" { title = "Looong Tab"; t14t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t13 "widget/tabpage" { title = "S"; t14t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t14 "widget/tabpage" { title = "Last Tab"; t14t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t2; + TabIndex = runclient(:t14t1:selected_index); + } + } + + t14l2 "widget/label" { x=270; y=75; width=220; height=40; font_size=16; style="bold"; text="No Translation"; } + t14t2 "widget/tab" + { + x = 260; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t22; + select_translate_along = 0; + select_translate_out = 0; + + t14t21 "widget/tabpage" { title = "First"; t14t21l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t22 "widget/tabpage" { title = "Looong Tab"; t14t22l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t23 "widget/tabpage" { title = "S"; t14t23l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t24 "widget/tabpage" { title = "Last Tab"; t14t24l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t3; + TabIndex = runclient(:t14t2:selected_index); + } + } + + t14l3 "widget/label" { x=30; y=215; width=220; height=40; font_size=16; style="bold"; text="Along 8"; } + t14t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + select_translate_along = 8; + select_translate_out = 0; + + t14t31 "widget/tabpage" { title = "First"; t14t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t32 "widget/tabpage" { title = "Looong Tab"; t14t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t33 "widget/tabpage" { title = "S"; t14t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t34 "widget/tabpage" { title = "Last Tab"; t14t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t4; + TabIndex = runclient(:t14t3:selected_index); + } + } + + t14l4 "widget/label" { x=270; y=215; width=220; height=40; font_size=16; style="bold"; text="Out 8"; } + t14t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t42; + select_translate_along = 0; + select_translate_out = 8; + + t14t41 "widget/tabpage" { title = "First"; t14t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t42 "widget/tabpage" { title = "Looong Tab"; t14t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t43 "widget/tabpage" { title = "S"; t14t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t44 "widget/tabpage" { title = "Last Tab"; t14t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t5; + TabIndex = runclient(:t14t4:selected_index); + } + } + + t14l5 "widget/label" { x=30; y=355; width=30; height=40; font_size=16; style="bold"; text="Along -8"; } + t14l5a "widget/label" { x=67; y=357; width=100; height=40; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t14t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + select_translate_along = -8; + select_translate_out = 0; + + t14t51 "widget/tabpage" { title = "First"; t14t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t52 "widget/tabpage" { title = "Looong Tab"; t14t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t53 "widget/tabpage" { title = "S"; t14t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t54 "widget/tabpage" { title = "Last Tab"; t14t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t6; + TabIndex = runclient(:t14t5:selected_index); + } + } + + t14l6 "widget/label" { x=270; y=355; width=30; height=40; font_size=16; style="bold"; text="Out -8"; } + t14l6a "widget/label" { x=307; y=357; width=100; height=40; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t14t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = 80; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t62; + select_translate_along = 0; + select_translate_out = -8; + + t14t61 "widget/tabpage" { title = "First"; t14t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t62 "widget/tabpage" { title = "Looong Tab"; t14t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t63 "widget/tabpage" { title = "S"; t14t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t64 "widget/tabpage" { title = "Last Tab"; t14t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t1; + TabIndex = runclient(:t14t6:selected_index); + } + } + } + } + } \ No newline at end of file From bb5d39ec069cd51c24b00137b57110c92ec8b219 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:11:27 -0700 Subject: [PATCH 039/101] Fix a bug in the textbutton widget that broke responsiveness. --- centrallix-os/sys/js/htdrv_textbutton.js | 1 - 1 file changed, 1 deletion(-) diff --git a/centrallix-os/sys/js/htdrv_textbutton.js b/centrallix-os/sys/js/htdrv_textbutton.js index d053453b1..bca2a6595 100644 --- a/centrallix-os/sys/js/htdrv_textbutton.js +++ b/centrallix-os/sys/js/htdrv_textbutton.js @@ -205,7 +205,6 @@ function tb_setmode(layer,mode) switch(mode) { case 0: /* no point no click */ - moveTo(layer,layer.orig_x,layer.orig_y); $(layer).find(".cell").css({'border-style':'solid', 'border-color':'transparent'}); /*if(cx__capabilities.Dom2CSS) { From c569d2dc1eba53f4f13ad690db9cb023a90378d3 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:18:56 -0700 Subject: [PATCH 040/101] Make datetime widget responsive. Update C to generate responsive CSS rules. Replace JS .w and .h values with getters that return the current height and width. Add a JS resize handler that updates open datetime dropdowns when the page is resized. --- centrallix-os/sys/js/htdrv_datetime.js | 25 ++++++++++++-- centrallix/htmlgen/htdrv_datetime.c | 45 ++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_datetime.js b/centrallix-os/sys/js/htdrv_datetime.js index c333f6446..88535c015 100644 --- a/centrallix-os/sys/js/htdrv_datetime.js +++ b/centrallix-os/sys/js/htdrv_datetime.js @@ -163,7 +163,6 @@ function dt_init(param){ htr_init_layer(c2,l,'dt'); //dt_tag_images(l.document, 'dt', l); htutil_tag_images(l,'dt',l,l); - l.w = w; l.h = h; l.bg = htr_extract_bgcolor(bg); l.ubg = bg; l.fg = param.foreground; @@ -200,7 +199,29 @@ function dt_init(param){ else l.form = wgtrFindContainer(l,"widget/form"); if (l.form) l.form.Register(l); - pg_addarea(l, -1, -1, getClipWidth(l)+3, getClipHeight(l)+3, 'dt', 'dt', 3); + + // Setup getters widths and heights. + l.__defineGetter__('w', () => parseInt(getComputedStyle(l).width)); + l.__defineGetter__('h', () => parseInt(getComputedStyle(l).height)); + + /** Setup the hover area. **/ + l.area = pg_addarea(l, -1, -1, l.w + 3, l.h + 3, 'dt', 'dt', 3); + l.area.__defineGetter__('width', () => l.w + 3); + l.area.__defineGetter__('height', () => l.h + 3); + + // Resize date selection dropdown automatically. + const resize_handler = (layer) => + { + if (layer.PaneLayer && htr_getvisibility(layer.PaneLayer) === 'inherit') + { + dt_collapse(layer); + dt_expand(layer); + } + }; + const resize_observer = new ResizeObserver(entries => { + for (const entry of entries) resize_handler(entry.target); + }); + resize_observer.observe(l); // Events ifc_init_widget(l); diff --git a/centrallix/htmlgen/htdrv_datetime.c b/centrallix/htmlgen/htdrv_datetime.c index 703ccbf5f..2161cda7b 100644 --- a/centrallix/htmlgen/htdrv_datetime.c +++ b/centrallix/htmlgen/htdrv_datetime.c @@ -203,10 +203,42 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) else strcpy(fgcolor,"black"); - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#dt%POSbtn { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; cursor:default; border:1px outset #e0e0e0; %STR }\n",id,x,y,w,h,z, bgcolor); - htrAddStylesheetItem_va(s,"\t#dt%POScon1 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:1px; TOP:1px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,w-20,h-2,z+1); - htrAddStylesheetItem_va(s,"\t#dt%POScon2 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:1px; TOP:1px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,w-20,h-2,z+1); + /** Write style headers. **/ + htrAddStylesheetItem_va(s, + "\t#dt%POSbtn { " + "overflow:hidden; " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "cursor:default; " + "border:1px outset #e0e0e0; " + "%STR " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z, + bgcolor + ); + htrAddStylesheetItem_va(s, + "\t.dt%POScon { " + "overflow:hidden; " + "position:absolute; " + "left:1px; " + "top:1px; " + "width:calc(100%% - 20px); " + "height:calc(100%% - 2px); " + "z-index:%POS; " + "}\n", + id, + z + 1 + ); /** Write named global **/ htrAddScriptGlobal(s, "dt_current", "null", 0); @@ -246,8 +278,8 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) htrAddBodyItem_va(s," \n",w-2); htrAddBodyItem(s, " \n"); htrAddBodyItem(s, "\n");*/ - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
\n",id); + htrAddBodyItem_va(s,"
\n", id, id); + htrAddBodyItem_va(s,"\n", id, id); htrAddBodyItem(s, "
\n"); /** Add the event handling scripts **/ @@ -301,4 +333,3 @@ htdtInitialize() return 0; } - From 5eba8bf37c5f1fd8d6b326d1eaa2b75bb19236fe Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:19:38 -0700 Subject: [PATCH 041/101] Make dropdown widget responsive. Clean up responsive CSS style generation in the C code. Replace numbers for dropdown modes with #define macros. Replace JS .w and .h values with getters that return the current height and width. Add a JS resize handler that updates open dropdowns when the page is resized. Add dd_remove_pane() to delete DOM nodes for the dropdown pane. Fix a DOM node leak in dd_colapse() by calling dd_remove_pane(). Add another resize observer to ensure that HidLayer responds properly. --- centrallix-os/sys/js/htdrv_dropdown.js | 78 +++++++++++++++- centrallix/htmlgen/htdrv_dropdown.c | 120 +++++++++++++++---------- 2 files changed, 147 insertions(+), 51 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_dropdown.js b/centrallix-os/sys/js/htdrv_dropdown.js index b3a23a8db..b3e0734c1 100644 --- a/centrallix-os/sys/js/htdrv_dropdown.js +++ b/centrallix-os/sys/js/htdrv_dropdown.js @@ -390,6 +390,9 @@ function dd_collapse(l) //pg_resize_area(l.area,getClipWidth(l)+1,getClipHeight(l)+1, -1, -1); htr_setvisibility(l.PaneLayer, 'hidden'); dd_current = null; + + /** Remove the pane to avoid leaking DOM nodes. **/ + dd_remove_pane(l); } } @@ -511,6 +514,17 @@ function dd_select_item(l,i,from) moveTo(l.HidLayer, 2, ((l.h-2) - pg_parah)/2); resizeTo(l.HidLayer, l.w, l.h); + // Set up responsive handling. + if (!l.HidLayer.resize_observer) + { + const { HidLayer } = l; + const resize_observer = HidLayer.resize_observer = new ResizeObserver(_ => { + moveTo(HidLayer, 2, ((l.h - 2) - pg_parah) / 2); + resizeTo(HidLayer, l.w, l.h); + }); + resize_observer.observe(l); + } + htr_setvisibility(l.HidLayer, 'inherit'); setClipWidth(l.HidLayer, l.w-21); htr_setvisibility(l.VisLayer, 'hidden'); @@ -775,6 +789,41 @@ function dd_create_pane(l) return p; } +/** Quick and dirty function to remove panes without leaking DOM nodes. **/ +function dd_remove_pane(l) + { + if (!l || !l.PaneLayer) return; + + const p = l.PaneLayer; + + // Function for removing elements. + const remove_node = (n) => { if (n && n.parentNode) n.parentNode.removeChild(n); } + + // Remove item DOM nodes if present. + if (l.Items && l.Items.length) + { + for (let i = 0; i < l.Items.length; i++) + { + remove_node(l.Items[i]); + l.Items[i] = null; + } + l.Items.length = 0; + l.Items = null; + } + + // Remove scrollbar/thumb layers if present. + remove_node(p.ScrLayer); + remove_node(p.BarLayer); + remove_node(p.TmbLayer); + + // Remove the pane root. + remove_node(p); + + // Clear references. + p.ScrLayer = p.BarLayer = p.TmbLayer = p.Items = p.mainlayer = null; + l.PaneLayer = null; + } + /// REPLACE ITEMS IN DROPDOWN function dd_add_items(l,ary) @@ -1194,7 +1243,6 @@ function dd_init(param) l.NumDisplay = param.numDisplay; l.Mode = param.mode; l.SQL = param.sql; - l.popup_width = param.popup_width?param.popup_width:param.width; l.VisLayer = param.c1; l.HidLayer = param.c2; htr_init_layer(l.VisLayer, l, 'ddtxt'); @@ -1244,7 +1292,6 @@ function dd_init(param) l.getfocushandler = dd_getfocus; l.bg = param.background; l.hl = param.highlight; - l.w = param.width; l.h = param.height; l.fieldname = param.fieldname; l.enabled = 'full'; if (l.Mode != 3) @@ -1268,11 +1315,34 @@ function dd_init(param) else if (imgs[i].src.substr(-13,5) == 'white') imgs[i].upimg = true; } - l.area = pg_addarea(l, -1, -1, getClipWidth(l)+3, - getClipHeight(l)+3, 'dd', 'dd', 3); if (l.form) l.form.Register(l); l.init_items = false; + // Setup getters widths and heights. + const width_ratio = param.popup_width / param.width; + l.__defineGetter__('w', () => parseInt(getComputedStyle(l).width)); + l.__defineGetter__('h', () => parseInt(getComputedStyle(l).height)); + l.__defineGetter__('popup_width', () => l.w * width_ratio); + + /** Setup the hover area. **/ + l.area = pg_addarea(l, -1, -1, l.w + 3, l.h + 3, 'dd', 'dd', 3); + l.area.__defineGetter__('width', () => l.w + 3); + l.area.__defineGetter__('height', () => l.h + 3); + + // Resize dropdown automatically. + const resize_handler = (layer) => + { + if (layer.PaneLayer && htr_getvisibility(layer.PaneLayer) === 'inherit') + { + dd_collapse(layer); + dd_expand(layer); + } + }; + const resize_observer = new ResizeObserver(entries => { + for (const entry of entries) resize_handler(entry.target); + }); + resize_observer.observe(l); + // Events var ie = l.ifcProbeAdd(ifEvent); ie.Add("MouseDown"); diff --git a/centrallix/htmlgen/htdrv_dropdown.c b/centrallix/htmlgen/htdrv_dropdown.c index 7f16bcbe5..f2af95d00 100644 --- a/centrallix/htmlgen/htdrv_dropdown.c +++ b/centrallix/htmlgen/htdrv_dropdown.c @@ -46,6 +46,12 @@ static struct { int idcnt; } HTDD; +/** Dropdown modes. **/ +#define HTDD_STATIC 0 +#define HTDD_DYNAMIC_SERVER 1 +#define HTDD_DYNAMIC 2 +#define HTDD_DYNAMIC_CLIENT HTDD_DYNAMIC +#define HTDD_OBJECTSOURCE 3 /* htddRender - generate the HTML code for the page. @@ -144,7 +150,7 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); - /** Ok, write the style header items. **/ + /** Write basic element CSS. **/ htrAddStylesheetItem_va(s, "\t#dd%POSbtn { " "OVERFLOW:hidden; " @@ -160,10 +166,10 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { "border:1px outset #e0e0e0; " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w, ht_get_total_w(tree), ht_get_fl_w(tree)), - ht_flex(h, ht_get_total_h(tree), ht_get_fl_h(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), z, bgstr ); @@ -171,35 +177,18 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { htrAddStylesheetItem_va(s,"\t#dd%POSbtn { color: %STR&CSSVAL; }\n",id,textcolor); } htrAddStylesheetItem_va(s, - "\t#dd%POScon1 { " - "OVERFLOW:hidden; " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:1px; " - "TOP:1px; " - "WIDTH:1024px; " - "HEIGHT:"ht_flex_format"; " - "Z-INDEX:%POS; " + "\t.dd%POScon { " + "overflow:hidden; " + "position:absolute; " + "left:1px; " + "top:1px; " + "width:1024px; " + "height:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, - ht_flex(h-2, h, 0.0), - z+1 - ); - /** I have no idea why we need dd#con2. It's hidden by default. **/ - htrAddStylesheetItem_va(s, - "\t#dd%POScon2 { " - "OVERFLOW:hidden; " - "POSITION:absolute; " - "VISIBILITY:hidden; " - "LEFT:1px; " - "TOP:1px; " - "WIDTH:1024px; " - "HEIGHT:"ht_flex_format"; " - "Z-INDEX:%POS; " - "}\n", - id, - ht_flex(h-2, h, 0.0), - z+1 + ht_flex(h - 2, h, 0.0), + z + 1 ); htrAddScriptGlobal(s, "dd_current", "null", 0); @@ -228,20 +217,20 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { /** Get the mode (default to 1, dynamicpage) **/ - mode = 0; + mode = HTDD_STATIC; if (wgtrGetPropertyValue(tree,"mode",DATA_T_STRING,POD(&ptr)) == 0) { - if (!strcmp(ptr,"static")) mode = 0; - else if (!strcmp(ptr,"dynamic_server")) mode = 1; - else if (!strcmp(ptr,"dynamic")) mode = 2; - else if (!strcmp(ptr,"dynamic_client")) mode = 2; - else if (!strcmp(ptr,"objectsource")) mode = 3; + if (strcmp(ptr, "static") == 0) mode = HTDD_STATIC; + else if (strcmp(ptr, "dynamic_server") == 0) mode = HTDD_DYNAMIC_SERVER; + else if (strcmp(ptr, "dynamic") == 0) mode = HTDD_DYNAMIC; + else if (strcmp(ptr, "dynamic_client") == 0) mode = HTDD_DYNAMIC_CLIENT; + else if (strcmp(ptr, "objectsource") == 0) mode = HTDD_OBJECTSOURCE; else { - mssError(1,"HTDD","Dropdown widget has not specified a valid mode."); + mssError(1, "HTDD", "Invalid dropdown widget 'mode' value: \"%s\"", ptr); return -1; } } - sql = 0; + sql = NULL; if (wgtrGetPropertyValue(tree,"sql",DATA_T_STRING,POD(&sql)) != 0 && mode != 0 && mode != 3) { mssError(1, "HTDD", "SQL parameter was not specified for dropdown widget"); return -1; @@ -249,7 +238,43 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { htrCheckAddExpression(s,tree,name,"sql"); /** Script initialization call. **/ - htrAddScriptInit_va(s," dd_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), c1:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"dd%POScon1\"), c2:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"dd%POScon2\"), background:'%STR&JSSTR', highlight:'%STR&JSSTR', fieldname:'%STR&JSSTR', numDisplay:%INT, mode:%INT, sql:'%STR&JSSTR', width:%INT, height:%INT, form:'%STR&JSSTR', osrc:'%STR&JSSTR', qms:%INT, ivs:%INT, popup_width:%INT});\n", name, name, id, name, id, bgstr, hilight, fieldname, num_disp, mode, sql?sql:"", w, h, form, osrc, query_multiselect, invalid_select_default, pop_w); + htrAddScriptInit_va(s, + "dd_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "c1:htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'dd%POScon1'), " + "c2:htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'dd%POScon2'), " + "background:'%STR&JSSTR', " + "highlight:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "numDisplay:%INT, " + "mode:%INT, " + "sql:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "osrc:'%STR&JSSTR', " + "qms:%INT, " + "ivs:%INT, " + "width:%INT, " + "height:%INT, " + "popup_width:%INT, " + "});\n", + name, + name, id, + name, id, + bgstr, + hilight, + fieldname, + num_disp, + mode, + (sql != NULL) ? sql : "", + form, + osrc, + query_multiselect, + invalid_select_default, + w, + ht_get_fl_x(tree), + h, + pop_w + ); /** HTML body
element for the layers. **/ htrAddBodyItem_va(s,"
\n" @@ -265,12 +290,12 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { htrAddBodyItem_va(s," \n",w-2); htrAddBodyItem(s, " \n"); htrAddBodyItem(s, "\n");*/ - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
\n",id); + htrAddBodyItem_va(s,"
\n", id, id); + htrAddBodyItem_va(s,"\n", id, id); htrAddBodyItem(s, "
\n"); /* Read and initialize the dropdown items */ - if (mode == 1) { + if (mode == HTDD_DYNAMIC_SERVER) { /** The result set from this SQL query can take two forms: positional or named. ** For Positional, the params are: label, value, selected, group, hidden. ** For Named, the above names can appear in any order. @@ -362,7 +387,7 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { objQueryClose(qy); } } - else if(mode==3) { + else if (mode == HTDD_OBJECTSOURCE) { /* get objects from form */ } @@ -373,7 +398,9 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { subtree = xaGetItem(&(tree->Children), i); if (!strcmp(subtree->Type, "widget/dropdownitem")) subtree->RenderFlags |= HT_WGTF_NOOBJECT; - if (!strcmp(subtree->Type,"widget/dropdownitem") && mode == 0) + + /** Write JS to render dropdown items in static mode. **/ + if (!strcmp(subtree->Type,"widget/dropdownitem") && mode == HTDD_STATIC) { if (wgtrGetPropertyValue(subtree,"label",DATA_T_STRING,POD(&ptr)) != 0) { @@ -459,4 +486,3 @@ int htddInitialize() { return 0; } - From 3327935d142d7edbae24844d3a9bb0809b803eb2 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:22:10 -0700 Subject: [PATCH 042/101] Make editbox widget responsive. Clean up responsive CSS style generation in the C code. Simplify JS to prevent it breaking the responsive CSS. --- centrallix-os/sys/js/htdrv_editbox.js | 4 +- centrallix/htmlgen/htdrv_editbox.c | 68 +++++++++++++++++---------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_editbox.js b/centrallix-os/sys/js/htdrv_editbox.js index 160ce0bdf..df6579be7 100644 --- a/centrallix-os/sys/js/htdrv_editbox.js +++ b/centrallix-os/sys/js/htdrv_editbox.js @@ -162,8 +162,10 @@ function eb_setdesc(txt) ({ "z-index":"-1", "color":this.desc_fgcolor?this.desc_fgcolor:"#808080", - "top":($(this).height() - $(this.DescLayer).height())/2 + "px", + "top":"0px", "left":(this.input_width() + ((this.content || this.has_focus)?4:0) + 5) + "px", + "height":"100%", + "align-content":"center", "visibility":"inherit", "white-space":"nowrap", }); diff --git a/centrallix/htmlgen/htdrv_editbox.c b/centrallix/htmlgen/htdrv_editbox.c index ef40c8d8a..f4b85c397 100644 --- a/centrallix/htmlgen/htdrv_editbox.c +++ b/centrallix/htmlgen/htdrv_editbox.c @@ -149,34 +149,36 @@ htebRender(pHtSession s, pWgtrNode tree, int z) box_offset = 0; /** Ok, write the style header items. **/ + const int base_w = w - (2 * box_offset); htrAddStylesheetItem_va(s, "\t#eb%POSbase { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, - ht_flex(x, ht_get_total_w(tree), ht_get_fl_x(tree)), - ht_flex(y, ht_get_total_h(tree), ht_get_fl_y(tree)), - ht_flex(w - 2 * box_offset, ht_get_total_w(tree), ht_get_fl_w(tree)), + ht_flex(x, ht_get_parent_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_parent_h(tree), ht_get_fl_y(tree)), + ht_flex(base_w, ht_get_parent_w(tree), ht_get_fl_w(tree)), z ); htrAddStylesheetItem_va(s, "\t#eb%POScon1 { " - "VISIBILITY:inherit; " - "LEFT:5px; " - "TOP:0px; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "position:absolute; " + "visibility:inherit; " + "left:5px; " + "top:0px; " + "width:calc(100%% - 10px); " + "height:100%%; " "border:none; " + "z-index:%POS; " "}\n", id, - ht_flex(w - 10, ht_get_total_w(tree), ht_get_fl_w(tree)), - z+1 + z + 1 ); /** Write named global **/ @@ -200,10 +202,28 @@ htebRender(pHtSession s, pWgtrNode tree, int z) htrAddEventHandlerFunction(s, "document","PASTE", "eb", "eb_paste"); /** Script initialization call. **/ - htrAddScriptInit_va(s, " eb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), c1:document.getElementById(\"eb%POScon1\"), form:\"%STR&JSSTR\", fieldname:\"%STR&JSSTR\", isReadOnly:%INT, mainBackground:\"%STR&JSSTR\", tooltip:\"%STR&JSSTR\", desc_fgcolor:\"%STR&JSSTR\", empty_desc:\"%STR&JSSTR\"});\n", - name, id, - form, fieldname, is_readonly, main_bg, - tooltip, descfg, descr); + htrAddScriptInit_va(s, + "eb_init({ " + "layer:wgtrGetNodeRef(ns,'%STR&SYM'), " + "c1:document.getElementById('eb%POScon1'), " + "form:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "isReadOnly:%INT, " + "mainBackground:'%STR&JSSTR', " + "tooltip:'%STR&JSSTR', " + "desc_fgcolor:'%STR&JSSTR', " + "empty_desc:'%STR&JSSTR', " + "});\n", + name, + id, + form, + fieldname, + is_readonly, + main_bg, + tooltip, + descfg, + descr + ); /** HTML body
element for the base layer. **/ htrAddBodyItem_va(s, "
\n",id); @@ -214,12 +234,12 @@ htebRender(pHtSession s, pWgtrNode tree, int z) else htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: gray white white gray; %STR }\n",id, main_bg); if (h >= 0) + { htrAddStylesheetItem_va(s, - "\t#eb%POSbase { height:"ht_flex_format"; }\n" - "\t#eb%POScon1 { height:"ht_flex_format"; }\n", - id, ht_flex(h - 2 * box_offset, ht_get_total_h(tree), ht_get_fl_h(tree)), - id, ht_flex(h - 2 * box_offset - 2, ht_get_total_h(tree), ht_get_fl_h(tree)) + "\t#eb%POSbase { height:"ht_flex_format"; }\n", + id, ht_flex(h - (2 * box_offset), ht_get_parent_h(tree), ht_get_fl_h(tree)) ); + } //htrAddBodyItem_va(s, "
 
\n", w-2, h-2); //htrAddBodyItem_va(s, "
\n",id); From de97b78c614a325c590c1067dcdeb6ef100e050f Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:22:35 -0700 Subject: [PATCH 043/101] Make formstatus widget responsive. --- centrallix/htmlgen/htdrv_formstatus.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/centrallix/htmlgen/htdrv_formstatus.c b/centrallix/htmlgen/htdrv_formstatus.c index 33258e3c1..b4b0d504c 100644 --- a/centrallix/htmlgen/htdrv_formstatus.c +++ b/centrallix/htmlgen/htdrv_formstatus.c @@ -89,7 +89,22 @@ int htfsRender(pHtSession s, pWgtrNode tree, int z) { htrAddWgtrObjLinkage_va(s, tree, "fs%POSmain", id); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#fs%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:13px; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); + htrAddStylesheetItem_va(s, + "\t#fs%POSmain { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:13px; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ); htrAddScriptInclude(s, "/sys/js/htdrv_formstatus.js", 0); From ddc1d354e74aa7dde932005e8755750af51bd7ec Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:24:39 -0700 Subject: [PATCH 044/101] Make imagebutton widget responsive. Update C generation to create responsive CSS for image button widgets. Disable unnecessary clipping in the imagebutton widget. --- centrallix-os/sys/js/htdrv_imagebutton.js | 1 + centrallix/htmlgen/htdrv_imagebutton.c | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/htdrv_imagebutton.js b/centrallix-os/sys/js/htdrv_imagebutton.js index c3aae6b2a..1122a2303 100644 --- a/centrallix-os/sys/js/htdrv_imagebutton.js +++ b/centrallix-os/sys/js/htdrv_imagebutton.js @@ -211,6 +211,7 @@ function ib_init(param) l.img.kind = 'ib'; l.cursrc = param.n; setClipWidth(l, w); + disableClippingCSS(l); l.trigger = ib_trigger; diff --git a/centrallix/htmlgen/htdrv_imagebutton.c b/centrallix/htmlgen/htdrv_imagebutton.c index 64cfe92eb..3f0bb5390 100644 --- a/centrallix/htmlgen/htdrv_imagebutton.c +++ b/centrallix/htmlgen/htdrv_imagebutton.c @@ -137,7 +137,23 @@ htibtnRender(pHtSession s, pWgtrNode tree, int z) button_repeat = htrGetBoolean(tree, "repeat", 0); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#ib%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; cursor:pointer; }\n",id,x,y,w,z); + htrAddStylesheetItem_va(s, + "\t#ib%POSpane { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden;" + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ); htrAddScriptGlobal(s, "ib_cur_img", "null", 0); htrAddWgtrObjLinkage_va(s, tree, "ib%POSpane", id); From f595e05a511f40f67bb0ed81e016c558f1b1e380 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:27:39 -0700 Subject: [PATCH 045/101] Make menu widget responsive. Update C generation to create responsive CSS. Update C style for script init. Update JS to only modify CSS when it is not provided in C. --- centrallix-os/sys/js/htdrv_menu.js | 10 ++-- centrallix/htmlgen/htdrv_menu.c | 75 +++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_menu.js b/centrallix-os/sys/js/htdrv_menu.js index f517e3948..1c25e62a3 100644 --- a/centrallix-os/sys/js/htdrv_menu.js +++ b/centrallix-os/sys/js/htdrv_menu.js @@ -395,19 +395,21 @@ function mn_init(param) menu.objname = param.name; menu.cur_highlight = null; + // Set up sizing. if (cx__capabilities.CSS2) { if (menu.scrollHeight == 0) { - pg_set_style(menu,'height',menu.childNodes[0].scrollHeight); - pg_set_style(menu,'width',menu.childNodes[0].scrollWidth); + if (param.h === -1) pg_set_style(menu, 'height', menu.childNodes[0].scrollHeight); + if (param.w === -1) pg_set_style(menu, 'width', menu.childNodes[0].scrollWidth); } else { - pg_set_style(menu,'height',menu.scrollHeight); - pg_set_style(menu,'width',menu.scrollWidth); + if (param.h === -1) pg_set_style(menu, 'height', menu.scrollHeight); + if (param.w === -1) pg_set_style(menu, 'width', menu.scrollWidth); } } + disableClippingCSS(menu); menu.act_w = getClipWidth(menu.clayer); menu.act_h = getClipHeight(menu.clayer); if ($(menu).css('visibility') == 'hidden' && (!menu.__WgtrParent.style || $(menu.__WgtrParent).css('visibility') == 'inherit')) diff --git a/centrallix/htmlgen/htdrv_menu.c b/centrallix/htmlgen/htdrv_menu.c index a400c885e..331b8bb92 100644 --- a/centrallix/htmlgen/htdrv_menu.c +++ b/centrallix/htmlgen/htdrv_menu.c @@ -284,21 +284,50 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) is_popup = htrGetBoolean(menu, "popup", 0); if (is_popup < 0) is_popup = 0; - /** Write the main style header item. **/ - htrAddStylesheetItem_va(s,"\t#mn%POSmain { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]%[WIDTH:%POSpx; %]Z-INDEX:%POS; }\n", id,is_popup?"hidden":"inherit", x, y, h != -1, h-2*bx, w != -1, w-2*bx, z); + /** Write styles for the main DOM element. **/ + htrAddStylesheetItem_va(s, + "\t#mn%POSmain { " + "position:absolute; " + "visibility:%STR; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "%[width:"ht_flex_format"; %]" + "z-index:%POS; " + "}\n", + id, + (is_popup) ? "hidden" : "inherit", + ht_flex_x(x, menu), + ht_flex_y(y, menu), + (h != -1), ht_flex_h(h - 2 * bx, menu), + (w != -1), ht_flex_w(w - 2 * bx, menu), + z + ); if (shadow_radius > 0) { htrAddStylesheetItem_va(s,"\t#mn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", id, shadow_offset, shadow_offset, shadow_radius, shadow_color); } - htrAddStylesheetItem_va(s,"\t#mn%POScontent { POSITION:absolute; VISIBILITY: inherit; LEFT:0px; TOP:0px; %[HEIGHT:%POSpx; %]%[WIDTH:%POSpx; %]Z-INDEX:%POS; }\n", id, h != -1, h-2*bx, w != -1, w-2*bx, z+1); if (s->Capabilities.CSS2) htrAddStylesheetItem_va(s,"\t#mn%POSmain { overflow:hidden; border-style: solid; border-width: 1px; border-color: white gray gray white; color:%STR; %STR }\n", id, textcolor, bgstr); - - /** content layer **/ + + /** Write styles for the content container. **/ + htrAddStylesheetItem_va(s, + "\t#mn%POScontent { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:0px; " + "height:%%100;" + "width:%%100;" + "z-index:%POS; " + "}\n", + id, + z + 1 + ); if (s->Capabilities.CSS2) htrAddStylesheetItem_va(s,"\t#mn%POScontent { overflow:hidden; cursor:default; }\n", id ); - /** highlight bar **/ + /** Write styles for the highlight bar. **/ htrAddStylesheetItem_va(s, "\t#mn%POShigh { POSITION:absolute; VISIBILITY: hidden; LEFT:0px; TOP:0px; Z-INDEX:%POS; }\n", id, z); if (s->Capabilities.CSS2) htrAddStylesheetItem_va(s,"\t#mn%POShigh { overflow:hidden; }\n", id ); @@ -324,10 +353,34 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) htrAddScriptInclude(s, "/sys/js/htdrv_menu.js", 0); /** Initialization **/ - htrAddScriptInit_va(s," mn_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), clayer:wgtrGetContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), hlayer:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"mn%POShigh\"), bgnd:\"%STR&JSSTR\", high:\"%STR&JSSTR\", actv:\"%STR&JSSTR\", txt:\"%STR&JSSTR\", w:%INT, h:%INT, horiz:%INT, pop:%INT, name:\"%STR&SYM\"});\n", - name, name, name, id, - bgstr, highlight, active, textcolor, - w, h, is_horizontal, is_popup, name); + htrAddScriptInit_va(s, + "mn_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "clayer:wgtrGetContainer(wgtrGetNodeRef(ns,'%STR&SYM')), " + "hlayer:htr_subel(wgtrGetNodeRef(ns,'%STR&SYM'), 'mn%POShigh'), " + "bgnd:'%STR&JSSTR', " + "high:'%STR&JSSTR', " + "actv:'%STR&JSSTR', " + "txt:'%STR&JSSTR', " + "w:%INT, " + "h:%INT, " + "horiz:%INT, " + "pop:%INT, " + "name:'%STR&SYM', " + "});\n", + name, + name, + name, id, + bgstr, + highlight, + active, + textcolor, + w, + h, + is_horizontal, + is_popup, + name + ); /** Event handlers **/ htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "mn", "mn_mousemove"); @@ -596,5 +649,3 @@ htmenuInitialize() return 0; } - - From 4af7bd8a6dc97a7932f0ae0b68e3506ad098cc4c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:28:11 -0700 Subject: [PATCH 046/101] Make objcanvas widget responsive (sort of). Update C generation to create responsive CSS. Update JS to give an error when creating a new layer, which will probably break responsive design. --- centrallix-os/sys/js/htdrv_objcanvas.js | 1 + centrallix/htmlgen/htdrv_objcanvas.c | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/htdrv_objcanvas.js b/centrallix-os/sys/js/htdrv_objcanvas.js index aeaa1fd1a..b32aa93e1 100644 --- a/centrallix-os/sys/js/htdrv_objcanvas.js +++ b/centrallix-os/sys/js/htdrv_objcanvas.js @@ -113,6 +113,7 @@ function oc_add_osrc_object(o) y = parseInt(y); // make and position the layer + console.error("New layer created. This will probably break responsive design."); var l = this.NewLayer(); htr_init_layer(l, this, 'oc'); l.osrc_oid = o.oid; diff --git a/centrallix/htmlgen/htdrv_objcanvas.c b/centrallix/htmlgen/htdrv_objcanvas.c index 6ff6078e8..5a1150533 100644 --- a/centrallix/htmlgen/htdrv_objcanvas.c +++ b/centrallix/htmlgen/htdrv_objcanvas.c @@ -106,7 +106,28 @@ htocRender(pHtSession s, pWgtrNode oc_node, int z) /** Add css item for the layer **/ if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#oc%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; overflow: hidden; %STR}\n",id,x,y,w,h,z,main_bg); + { + htrAddStylesheetItem_va(s, + "\t#oc%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "%STR " + "}\n", + id, + ht_flex_x(x, oc_node), + ht_flex_y(y, oc_node), + ht_flex_w(w, oc_node), + ht_flex_h(h, oc_node), + z, + main_bg + ); + } else htrAddStylesheetItem_va(s,"\t#oc%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; HEIGHT:%POS; Z-INDEX:%POS; }\n",id,x,y,w,h,z); From c0a7bb8b1f8bd673b413e4e4947e24daf589bcd8 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:31:26 -0700 Subject: [PATCH 047/101] Make radiobutton pannel widget responsive. Update C generation to create responsive CSS. Remove unnecessary clip rectangle CSS styles. Clean up HTML & CSS generation code. Add width and height getters in JS that return the current width and height of the widget, allowing areas to resize properly. --- centrallix-os/sys/js/htdrv_radiobutton.js | 4 +- centrallix/htmlgen/htdrv_radiobutton.c | 191 +++++++++++++++++----- 2 files changed, 149 insertions(+), 46 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_radiobutton.js b/centrallix-os/sys/js/htdrv_radiobutton.js index 5b106deb9..2d5c9eb77 100644 --- a/centrallix-os/sys/js/htdrv_radiobutton.js +++ b/centrallix-os/sys/js/htdrv_radiobutton.js @@ -152,7 +152,9 @@ function add_radiobutton(optionPane, param) { } optionPane.yOffset = getRelativeY(optionPane)+getRelativeY(rb.coverPane)+getRelativeY(rb.borderPane); - pg_addarea(rb, getRelativeX(optionPane), optionPane.yOffset, getClipWidth(optionPane), pg_parah+4, optionPane, 'rb', 3); + optionPane.area = pg_addarea(rb, getRelativeX(optionPane), optionPane.yOffset, getClipWidth(optionPane), pg_parah+4, optionPane, 'rb', 3); + optionPane.area.__defineGetter__('width', () => parseInt(getComputedStyle(optionPane).width)); + optionPane.area.__defineGetter__('height', () => parseInt(getComputedStyle(optionPane).height)); } function rb_getfocus(xo,yo,l,c,n,a,from_kbd) diff --git a/centrallix/htmlgen/htdrv_radiobutton.c b/centrallix/htmlgen/htdrv_radiobutton.c index c1d395c06..d5258ed5e 100644 --- a/centrallix/htmlgen/htdrv_radiobutton.c +++ b/centrallix/htmlgen/htdrv_radiobutton.c @@ -9,6 +9,7 @@ #include "cxlib/xhash.h" #include "cxlib/mtsession.h" #include "cxlib/strtcpy.h" +#include "cxlib/util.h" /************************************************************************/ /* Centrallix Application Server System */ @@ -61,13 +62,9 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { char form[64]; pWgtrNode radiobutton_obj, sub_tree; int x=-1,y=-1,w,h; - int top_offset; - int cover_height, cover_width; - int item_spacing; int id, i, j; int is_selected; - int rb_cnt; - int cover_margin; + int raido_button_count; char fieldname[32]; char value[64]; char label[64]; @@ -137,19 +134,74 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { htrAddScriptInclude(s, "/sys/js/htdrv_radiobutton.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - /** Ok, write the style header items. **/ - top_offset = s->ClientInfo->ParagraphHeight*3/4+1; - cover_height = h-(top_offset+3+2); - cover_width = w-(2*3 +2); - htrAddStylesheetItem_va(s,"\t#rb%POSparent { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,x,y,w,h,z,w,h); - htrAddStylesheetItem_va(s,"\t#rb%POSborder { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,3,top_offset,w-(2*3),h-(top_offset+3),z+1,w-(2*3),h-(top_offset+3)); - htrAddStylesheetItem_va(s,"\t#rb%POScover { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,1,1,cover_width,cover_height,z+2,cover_width,cover_height); - htrAddStylesheetItem_va(s,"\t#rb%POStitle { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n", - id,10,1,w/2,s->ClientInfo->ParagraphHeight,z+3); - + /** Write style headers for container DOM nodes. **/ + const int para_height = s->ClientInfo->ParagraphHeight; + fprintf(stderr, "h: %d\n", para_height); + const int top_offset = (para_height * 3) / 4 + 1; + htrAddStylesheetItem_va(s, + "#rb%POSparent { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ); + htrAddStylesheetItem_va(s, + "#rb%POSborder { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:3px; " + "top:%POSpx; " + "width:calc(100%% - 6px); " + "height:calc(100%% - %POSpx); " + "z-index:%POS; " + "}\n", + id, + top_offset, + top_offset + 3, + z + 1 + ); + htrAddStylesheetItem_va(s, + "#rb%POScover { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:1px; " + "top:1px; " + "width:calc(100%% - 2px); " + "height:calc(100%% - 2px); " + "z-index:%POS; " + "}\n", + id, + z + 2 + ); + htrAddStylesheetItem_va(s, + "#rb%POStitle { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:10px; " + "top:1px; " + "width:50%%; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, + para_height, + z + 3 + ); + htrAddScriptGlobal(s, "radiobutton", "null", 0); /** DOM linkages **/ @@ -157,27 +209,23 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(htr_subel(_obj,\"rb%POSborder\"),\"rb%POScover\")",id,id); /** Loop through each radiobutton and flag it NOOBJECT **/ - rb_cnt = 0; + raido_button_count = 0; for (j=0;jChildren));j++) { radiobutton_obj = xaGetItem(&(tree->Children), j); radiobutton_obj->RenderFlags |= HT_WGTF_NOOBJECT; wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) - { - rb_cnt++; - } + if (strcmp(ptr,"widget/radiobutton") == 0) raido_button_count++; } - /* - Now lets loop through and create a style sheet for each optionpane on the - radiobuttonpanel - */ - item_spacing = 12 + s->ClientInfo->ParagraphHeight; - cover_margin = 10; - if (item_spacing*rb_cnt+2*cover_margin > cover_height) - item_spacing = (cover_height-2*cover_margin)/rb_cnt; - if (item_spacing*rb_cnt+2*cover_margin > cover_height) - cover_margin = (cover_height-(item_spacing*rb_cnt))/2; + + /** Compute values for laying out radio buttons. **/ + const int cover_height = h - top_offset - 6; + int item_spacing = para_height + 12; + int cover_margin = 10; + if (item_spacing * raido_button_count + 2*cover_margin > cover_height) + item_spacing = (cover_height-2*cover_margin)/raido_button_count; + if (item_spacing * raido_button_count + 2*cover_margin > cover_height) + cover_margin = (cover_height-(item_spacing*raido_button_count))/2; if (cover_margin < 2) cover_margin = 2; i = 1; for (j=0;jChildren));j++) @@ -186,8 +234,22 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); if (!strcmp(ptr,"widget/radiobutton")) { - htrAddStylesheetItem_va(s,"\t#rb%POSoption%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px, %POSpx, %POSpx, 0px); }\n", - id,i,7,cover_margin+((i-1)*item_spacing)+3,cover_width-7,item_spacing,z+2,cover_width-7,item_spacing); + htrAddStylesheetItem_va(s, + "#rb%POSoption%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:7px; " + "top:%INTpx; " + "width:calc(100%% - 7px); " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, i, + cover_margin + ((i-1) * item_spacing) + 2, + min(item_spacing, para_height + 4), + z + 2 + ); i++; } } @@ -266,19 +328,58 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { if (!strcmp(ptr,"widget/radiobutton")) { /** CSS layers **/ - htrAddStylesheetItem_va(s,"\t#rb%POSbuttonset%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,5,2+(s->ClientInfo->ParagraphHeight-12)/2,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSbuttonunset%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,5,2+(s->ClientInfo->ParagraphHeight-12)/2,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSvalue%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,i,5,5,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSlabel%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,27,2,cover_width-(27+1),item_spacing-1,z+2,cover_width-(27+1),item_spacing-1); + htrAddStylesheetItem_va(s, + "#rb%POSbuttonset%POS, " + "#rb%POSbuttonunset%POS { " + "position:absolute; " + "overflow:hidden; " + "left:5px; " + "top:%INTpx; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + id, i, + (para_height / 2) - 3, + z + 2 + ); + htrAddStylesheetItem_va(s, + "#rb%POSvalue%POS { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:5px; " + "top:6px; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "}\n", + id, i, + z + 2 + ); + htrAddStylesheetItem_va(s, + "#rb%POSlabel%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:27px; " + "top:3px; " + "width:calc(100%% - 27px); " + "height:%POSpx; " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + item_spacing - 1, + z + 2 + ); /** Body layers **/ htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); + htrAddBodyItem_va(s,"
\n", id, i); + htrAddBodyItem_va(s,"
\n", id, i); wgtrGetPropertyValue(radiobutton_obj,"label",DATA_T_STRING,POD(&ptr)); strtcpy(sbuf2,ptr,sizeof(sbuf2)); From 19383f93fabe13a5d82ed36b9ea2b13d00e2c269 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:35:39 -0700 Subject: [PATCH 048/101] Remove unnecessary width from htdrv_treeview.js. --- centrallix-os/sys/js/htdrv_treeview.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_treeview.js b/centrallix-os/sys/js/htdrv_treeview.js index b3d4f1873..fb89579fa 100644 --- a/centrallix-os/sys/js/htdrv_treeview.js +++ b/centrallix-os/sys/js/htdrv_treeview.js @@ -38,7 +38,6 @@ function tv_new_layer(width,pdoc,l) else if(cx__capabilities.Dom1HTML) { nl = document.createElement('DIV'); - if (width) nl.style.width = width + 'px'; nl.className = l.divclass; //setClip(0, width, 0, 0); pg_set_style(nl, 'position','absolute'); @@ -1297,7 +1296,7 @@ function tv_mouseover(e) if (e.kind == 'tv') { cn_activate(e.mainlayer, 'MouseOver'); - if (getClipWidth(e.layer) <= getdocWidth(e.layer)+2 && e.layer.link_txt) + if (e.layer.link_txt) e.layer.tipid = pg_tooltip(e.layer.link_txt, e.pageX, e.pageY); } return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; From 59bed9aaaf5479da9834fc6af3effb77849b70cb Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:38:25 -0700 Subject: [PATCH 049/101] Make window widget responsive. Clean up C code for generation and remove dead clutter. Improve readability of wn_do_move(). Add wn_do_move_internal(). Add resize observer to keep windows on screen when the page is resized. --- centrallix-os/sys/js/htdrv_window.js | 118 +++++++++---- centrallix/htmlgen/htdrv_window.c | 249 +++++++++++++++++---------- 2 files changed, 242 insertions(+), 125 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_window.js b/centrallix-os/sys/js/htdrv_window.js index 6cdcf0da9..a0b2367a9 100644 --- a/centrallix-os/sys/js/htdrv_window.js +++ b/centrallix-os/sys/js/htdrv_window.js @@ -136,6 +136,24 @@ function wn_init(param) pg_addsched_fn(window, "pg_reveal_event", [l,l,'Reveal'], 0); } + // Setup responsive movement. + // resize_listener.params stores the params from the last time the window + // was moved, allowing us to reuse them to call wn_do_move_internal(), + // allowing that function to recalculate the window position based on the + // new viewport size. + const resize_listener = l.resize_listener = {}; + resize_listener.handler = () => + { + const { pg_attract, wn_new_x, wn_new_y } = l.resize_listener.params; + wn_do_move_internal(l, pg_attract, wn_new_x, wn_new_y); + }; + resize_listener.params = { + pg_attract: 0, + wn_new_x: getPageX(l), + wn_new_y: getPageY(l), + }; + window.addEventListener('resize', resize_listener.handler); + // force on page... if (getPageY(l) + l.orig_height > getInnerHeight()) { @@ -658,33 +676,71 @@ function wn_setvisibility(aparam) } } -function wn_domove2() - { +/*** This function does movement without worrying about global variables, + *** resize observers, etc. It just takes params and does movement. + *** + *** This function does handle snapping to edges and preventing windows from + *** being moved too far outside the viewport. + *** + *** @param wn_current The window to affect. + *** @param pg_attract The number of pixels from the edge of the viewport at + *** which windows snap to the edge. (Seems to always be 0.) + *** @param wn_new_x The new x coordinate for moving the window. + *** @param wn_new_y The new y coordinate for moving the window. + ***/ +function wn_do_move_internal(wn_current, pg_attract, wn_new_x, wn_new_y) + { + /** Get useful values. **/ + const { innerWidth, innerHeight } = window; + const window_width = getClipWidth(wn_current); + const window_height = getClipHeight(wn_current); + + /** Calculate available width and height, taking the sizes of scrollbars into account. **/ + const available_width = innerWidth - ((document.height - innerHeight - 2 >= 0) ? 15 : 0); + const available_height = innerHeight - ((document.width - innerWidth - 2 >= 0) ? 15 : 0); + let new_x, new_y; + + /** X: Handle snapping to edges. **/ + if (Math.isBetween(-pg_attract, wn_new_x, pg_attract)) new_x = 0; + else if (Math.isBetween(available_width - pg_attract, wn_new_x + window_width, available_width + pg_attract)) + new_x = available_width - window_width; + + /** X: Prevent windows getting lost off the left side of the page. **/ + else if (wn_new_x + window_width < 24) new_x = 24 - window_width; + else if (wn_new_x > available_width - 32) new_x = available_width - 32; + + /** X: Default case, no movement needed. **/ + else new_x = wn_new_x; + + /** Y: Handle snapping to edges. **/ + if (Math.isBetween(-pg_attract, wn_new_y, pg_attract)) new_y = 0; + else if (Math.isBetween(available_height - pg_attract, wn_new_y + window_height, available_height + pg_attract)) + new_y = available_height - window_height; + + /** Y: Prevent windows from going too far off the screen. **/ + else new_y = Math.clamp(0, wn_new_y, available_height - 24); + + /** Move the window to the new location. **/ + moveToAbsolute(wn_current, new_x, new_y); + /** Clicking and dragging a window is not a click. **/ + wn_current.clicked = 0; } -function wn_domove() +function wn_do_move() { - if (wn_current != null) - { - var ha=(document.height-window.innerHeight-2)>=0?15:0; - var va=(document.width-window.innerWidth-2)>=0?15:0; - var newx,newy; - if (wn_newx < pg_attract && wn_newx > -pg_attract) newx = 0; - else if (wn_newx+getClipWidth(wn_current) > window.innerWidth-ha-pg_attract && wn_newx+ getClipWidth(wn_current) < window.innerWidth-ha+pg_attract) - newx = window.innerWidth-ha-pg_get_style(wn_current,'clip.width'); - else if (wn_newx+getClipWidth(wn_current) < 25) newx = 25-pg_get_style(wn_current,'clip.width'); - else if (wn_newx > window.innerWidth-35-ha) newx = window.innerWidth-35-ha; - else newx = wn_newx; - if (wn_newy<0) newy = 0; - else if (wn_newy > window.innerHeight-12-va) newy = window.innerHeight-12-va; - else if (wn_newy < pg_attract) newy = 0; - else if (wn_newy+getClipHeight(wn_current) > window.innerHeight-va-pg_attract && wn_newy+getClipHeight(wn_current) < window.innerHeight-va+pg_attract) - newy = window.innerHeight-va-pg_get_style(wn_current,'clip.height'); - else newy = wn_newy; - moveToAbsolute(wn_current,newx,newy); - wn_current.clicked = 0; - } + /** Dereference globals once for performance. **/ + const { wn_current, pg_attract, wn_new_x, wn_new_y } = window; + + /** No window is selected, so we don't have to move anything. **/ + if (wn_current === null) return true; + + /** Call the unresponsive version. */ + wn_do_move_internal(wn_current, pg_attract, wn_new_x, wn_new_y); + + /** Update params for future resize calls. */ + wn_current.resize_listener.params = { pg_attract, wn_new_x, wn_new_y }; + return true; } @@ -736,8 +792,8 @@ function wn_mousedown(e) wn_current = e.mainlayer; wn_msx = e.pageX; wn_msy = e.pageY; - wn_newx = null; - wn_newy = null; + wn_new_x = null; + wn_new_y = null; wn_moved = 0; if (!cx__capabilities.Dom0IE) wn_windowshade_ns_moz(e.mainlayer); return EVENT_CONTINUE | EVENT_PREVENT_DEFAULT_ACTION; @@ -787,17 +843,17 @@ function wn_mousemove(e) wn_current.clicked = 0; if (wn_current.tid) clearTimeout(wn_current.tid); wn_current.tid = null; - if (wn_newx == null) + if (wn_new_x == null) { - wn_newx = getPageX(wn_current) + e.pageX-wn_msx; - wn_newy = getPageY(wn_current) + e.pageY-wn_msy; + wn_new_x = getPageX(wn_current) + e.pageX-wn_msx; + wn_new_y = getPageY(wn_current) + e.pageY-wn_msy; } else { - wn_newx += (e.pageX - wn_msx); - wn_newy += (e.pageY - wn_msy); + wn_new_x += (e.pageX - wn_msx); + wn_new_y += (e.pageY - wn_msy); } - setTimeout(wn_domove,60); + wn_do_move(); wn_moved = 1; wn_msx = e.pageX; wn_msy = e.pageY; diff --git a/centrallix/htmlgen/htdrv_window.c b/centrallix/htmlgen/htdrv_window.c index a02da42bb..5b6fbcd5d 100644 --- a/centrallix/htmlgen/htdrv_window.c +++ b/centrallix/htmlgen/htdrv_window.c @@ -3,8 +3,10 @@ #include #include #include + #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -59,14 +61,11 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - pWgtrNode sub_tree; - int x,y,w,h; - int tbw,tbh,bx,by,bw,bh; int id, i; int visible = 1; - char bgnd_style[128] = ""; - char hdr_bgnd_style[128] = ""; - char txtcolor[64] = ""; + char background_style[128] = ""; + char header_background_style[128] = ""; + char text_color[64] = ""; int has_titlebar = 1; char title[128]; int is_dialog_style = 0; @@ -76,7 +75,7 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) int is_toplevel = 0; int is_modal = 0; char icon[128]; - int shadow_offset,shadow_radius,shadow_angle; + int shadow_offset, shadow_radius, shadow_angle; char shadow_color[128]; int border_radius; char border_color[128]; @@ -99,6 +98,7 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) id = (HTWIN.idcnt++); /** Get x,y,w,h of this object **/ + int x, y, w, h; if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x = 0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y = 0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) @@ -156,17 +156,17 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) is_modal = htrGetBoolean(tree, "modal", 0); /** Check background color **/ - htrGetBackground(tree, NULL, 1, bgnd_style, sizeof(bgnd_style)); + htrGetBackground(tree, NULL, 1, background_style, sizeof(background_style)); /** Check header background color/image **/ - if (htrGetBackground(tree, "hdr", 1, hdr_bgnd_style, sizeof(hdr_bgnd_style)) < 0) - strcpy(hdr_bgnd_style, bgnd_style); + if (htrGetBackground(tree, "hdr", 1, header_background_style, sizeof(header_background_style)) < 0) + strcpy(header_background_style, background_style); /** Check title text color. **/ if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(txtcolor,ptr,sizeof(txtcolor)); + strtcpy(text_color, ptr, sizeof(text_color)); else - strcpy(txtcolor,"black"); + strcpy(text_color, "black"); /** Check window title. **/ if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) @@ -208,79 +208,95 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) } /** Compute titlebar width & height - includes edge below titlebar. **/ - if (has_titlebar) - { - tbw = w-2; - if (is_dialog_style || !s->Capabilities.Dom0NS) - tbh = 24; - else - tbh = 23; - } - else - { - tbw = w-2; - tbh = 0; - } - - /** Compute window body geometry **/ - if (is_dialog_style) - { - bx = 1; - by = 1+tbh; - bw = w-2; - bh = h-tbh-2; - } - else - { - bx = 2; - bw = w-4; - if (has_titlebar) - { - by = 1+tbh; - bh = h-tbh-3; - } - else - { - by = 2; - bh = h-4; - } - } + int title_bar_height = (has_titlebar) ? ((is_dialog_style) ? 24 : 23) : 0; /** Draw the main window layer and outer edge. **/ - /*htrAddStylesheetItem_va(s,"\t#wn%POSbase { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS;}\n", - id,visible?"inherit":"hidden",x,y,w-2*box_offset,h-2*box_offset, w, h, z+100);*/ - htrAddStylesheetItem_va(s,"\t#wn%POSbase { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; overflow: hidden; Z-INDEX:%POS;}\n", - id,visible?"inherit":"hidden",x,y,w-2*box_offset,h-2*box_offset, z+100); - htrAddStylesheetItem_va(s,"\t#wn%POSbase { border-style: %STR&CSSVAL; border-width: %INTpx; border-color: %STR&CSSVAL; border-radius: %INTpx; }\n", - id, border_style, border_width, border_color, border_radius); + /*** We don't even bother making these styles flex responsively because + *** they will be overwritten by the JS anyway. + ***/ + htrAddStylesheetItem_va(s, + "\t#wn%POSbase { " + "position:absolute; " + "visibility:%STR; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "overflow:hidden; " + "z-index:%POS; " + "border-style:%STR&CSSVAL; " + "border-width:%INTpx; " + "border-color:%STR&CSSVAL; " + "border-radius:%INTpx; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + x, + y, + w - 2 * box_offset, + h - 2 * box_offset, + z + 100, + border_style, + border_width, + border_color, + border_radius + ); if (shadow_radius > 0) { - htrAddStylesheetItem_va(s,"\t#wn%POSbase { box-shadow: %DBLpx %DBLpx %POSpx %STR&CSSVAL; }\n", id, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color); - } - - /** draw titlebar div **/ - if (has_titlebar) - { - htrAddStylesheetItem_va(s,"\t#wn%POStitlebar { POSITION: absolute; VISIBILITY: inherit; LEFT: 0px; TOP: 0px; HEIGHT: %POSpx; WIDTH: 100%%; overflow: hidden; Z-INDEX: %POS; color:%STR&CSSVAL; cursor:default; %STR}\n", id, tbh-1-box_offset, z+1, txtcolor, hdr_bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POStitlebar { border-style: solid; border-width: 0px 0px 1px 0px; border-color: gray; }\n", id); + double shadow_angle_radians = (double)shadow_angle * M_PI/180; + htrAddStylesheetItem_va(s, + "\t#wn%POSbase { box-shadow: %DBLpx %DBLpx %POSpx %STR&CSSVAL; }\n", id, + sin(shadow_angle_radians)*shadow_offset, cos(shadow_angle_radians)*(-shadow_offset), shadow_radius, shadow_color + ); } /** inner structure depends on dialog vs. window style **/ + int main_width, main_height, clip_height, dialogue_width; + char* border_color_str; if (is_dialog_style) { /** window inner container -- dialog **/ - htrAddStylesheetItem_va(s,"\t#wn%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:%INTpx; WIDTH: %POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS; %STR}\n", - id, tbh?(tbh-1):0, w-2, h-tbh-1, w, h-tbh+1, z+1, bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POSmain { border-style: solid; border-width: %POSpx 0px 0px 0px; border-color: white; }\n", id, has_titlebar?1:0); + main_width = w - 2; + main_height = h - title_bar_height - 1; + clip_height = h - title_bar_height + 1; + dialogue_width = 0; + border_color_str = "white"; } else { /** window inner container -- window **/ - htrAddStylesheetItem_va(s,"\t#wn%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:%INTpx; WIDTH: %POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS; %STR}\n", - id, tbh?(tbh-1):0, w-2-2*box_offset, h-tbh-(has_titlebar?1:2)-(has_titlebar?1:2)*box_offset, w, h-tbh+(has_titlebar?1:0)-2*box_offset, z+1, bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POSmain { border-style: solid; border-width: %POSpx 1px 1px 1px; border-color: gray white white gray; }\n", id, has_titlebar?0:1); + main_width = w - 2 * (box_offset + 1); + main_height = h - title_bar_height - (box_offset + 1) * ((has_titlebar) ? 1 : 2); + clip_height = h - title_bar_height + ((has_titlebar) ? 1 : 0) - 2 * box_offset; + dialogue_width = 1; + border_color_str = "gray white white gray"; } + htrAddStylesheetItem_va(s, + "\t#wn%POSmain { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "overflow:hidden; " + "clip:rect(0px, %INTpx, %INTpx, 0px); " + "border-style:solid; " + "border-color:%STR; " + "border-width:%POSpx %POSpx %POSpx %POSpx; " + "z-index:%POS; " + "%STR" + "}\n", + id, + max(title_bar_height - 1, 0), + main_width, + main_height, + w, clip_height, + border_color_str, + (has_titlebar) ? 1 : 0, dialogue_width, dialogue_width, dialogue_width, + z + 1, + background_style + ); /** Write globals for internal use **/ htrAddScriptGlobal(s, "wn_top_z","10000",0); @@ -295,7 +311,7 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) htrAddScriptGlobal(s, "wn_clicked","0",0); /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "wn%POSbase",id); + htrAddWgtrObjLinkage_va(s, tree, "wn%POSbase", id); htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, \"wn%POSmain\")",id); htrAddScriptInclude(s, "/sys/js/htdrv_window.js", 0); @@ -312,36 +328,81 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "wn", "wn_mouseout"); /** Script initialization call. **/ + htrAddScriptInit_va(s, + "wn_init({ " + "mainlayer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "clayer:wgtrGetContainer(wgtrGetNodeRef(ns, '%STR&SYM')), " + "titlebar:%[htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'wn%POStitlebar')%]%[null%], " + "gshade:%INT, " + "closetype:%INT, " + "toplevel:%INT, " + "modal:%INT, " + "});\n", + name, + name, + has_titlebar, name, id, !has_titlebar, + gshade, + closetype, + is_toplevel, + is_modal + ); + + /** Write HTML for the child window. **/ + htrAddBodyItem_va(s, "
\n", id); if (has_titlebar) { - htrAddScriptInit_va(s," wn_init({mainlayer:wgtrGetNodeRef(ns,'%STR&SYM'), clayer:wgtrGetContainer(wgtrGetNodeRef(ns,'%STR&SYM')), gshade:%INT, closetype:%INT, toplevel:%INT, modal:%INT, titlebar:htr_subel(wgtrGetNodeRef(ns,'%STR&SYM'),'wn%POStitlebar')});\n", - name,name,gshade,closetype, is_toplevel, is_modal, name, id); + /** Write styles and HTML for the title bar. **/ + htrAddStylesheetItem_va(s, + "\t#wn%POStitlebar { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:0px; " + "height:%POSpx; " + "width:calc(100%% - 4px); " + "overflow:hidden; " + "z-index:%POS; " + "color:%STR&CSSVAL; " + "cursor:default; " + "border-style:solid; " + "border-width:0px 0px 1px 0px; " + "border-color:gray; " + "%STR" + "}\n", + id, + title_bar_height - 1 - box_offset, + z + 1, + text_color, + header_background_style + ); + htrAddBodyItem_va(s, + "
" + "" + "
%STR&HTE
" + "" + "
\n", + id, + icon, + text_color, title + ); } - else - { - htrAddScriptInit_va(s," wn_init({mainlayer:wgtrGetNodeRef(ns,'%STR&SYM'), clayer:wgtrGetNodeRef(ns,'%STR&SYM'), gshade:%INT, closetype:%INT, toplevel:%INT, modal:%INT, titlebar:null});\n", - name,name,gshade,closetype, is_toplevel, is_modal); - } - - /** HTML body
elements for the layers. **/ - htrAddBodyItem_va(s,"
\n",id); - if (has_titlebar) - { - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
 %STR&HTE
\n", - tbh-1, tbw-2, icon, txtcolor, title); - htrAddBodyItem(s, "
\n"); - } - htrAddBodyItem_va(s,"
\n",id); - /** Check for more sub-widgets within the page. **/ + /** Write HTML for child widgets in the window. **/ + htrAddBodyItem_va(s,"
\n",id); for (i=0;iChildren));i++) { - sub_tree = xaGetItem(&(tree->Children), i); - htrRenderWidget(s, sub_tree, z+2); + /** TODO: Israel - Rewrite this using util.h, once its updated from the dups branch. **/ + const pWgtrNode child = xaGetItem(&(tree->Children), i); + const int ret = htrRenderWidget(s, child, z + 2); + if (ret != 0) + { + mssError(0, "HTWIN", + "Failed to render child widget '%s:%s' with error code %d.", + child->Namespace, child->Name, ret + ); + } } - - htrAddBodyItem(s,"
\n"); + htrAddBodyItem(s,"
\n"); return 0; } From 4a80bc203f8e4b97c339b2eca80ae952e0a2c550 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:40:16 -0700 Subject: [PATCH 050/101] Make active areas responsive. Add resize observer to htdrv_page.js to update areas/boxes when the page is resized. Fix undefined layer edge case in pg_setkbdfocus(). Improve code readability. --- centrallix-os/sys/js/htdrv_page.js | 96 ++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_page.js b/centrallix-os/sys/js/htdrv_page.js index 85c58c5b5..3023ea47c 100755 --- a/centrallix-os/sys/js/htdrv_page.js +++ b/centrallix-os/sys/js/htdrv_page.js @@ -51,6 +51,11 @@ function pg_scriptavailable(s) } +/** Quick and dirty resize handling tool. **/ +const resize_handlers = {}; +window.addEventListener('resize', (e) => Object.values(resize_handlers).forEach(f => f(e))); + + //START SECTION: DOM/CSS helper functions ----------------------------------- /** returns an attribute of the element in pixels **/ @@ -1929,7 +1934,9 @@ function pg_findfocusarea(l, xo, yo) function pg_setmousefocus(l, xo, yo) { - var a = pg_findfocusarea(l, xo, yo); + if (!l) return false; + + const a = pg_findfocusarea(l, xo, yo); if (a && a != pg_curarea) { pg_curarea = a; @@ -1937,26 +1944,52 @@ function pg_setmousefocus(l, xo, yo) { if (!pg_curarea.layer.getmousefocushandler || pg_curarea.layer.getmousefocushandler(xo, yo, a.layer, a.cls, a.name, a)) { - // wants mouse focus - var offs = $(pg_curarea.layer).offset(); - //var x = getPageX(pg_curarea.layer)+pg_curarea.x; - //var y = getPageY(pg_curarea.layer)+pg_curarea.y; - var x = offs.left+pg_curarea.x; - var y = offs.top+pg_curarea.y; - - var w = pg_curarea.width; - var h = pg_curarea.height; - if (cx__capabilities.Dom0NS) + // Create a function to handle all box updates with this focus. + const update_box = (area) => { - pg_mkbox(l, x,y,w,h, 1, document.layers.pgtop,document.layers.pgbtm,document.layers.pgrgt,document.layers.pglft, page.mscolor1, page.mscolor2, document.layers.pgktop.zIndex-1); - } - else if (cx__capabilities.Dom1HTML) - { - pg_mkbox(l, x,y,w,h, 1, document.getElementById("pgtop"),document.getElementById("pgbtm"),document.getElementById("pgrgt"),document.getElementById("pglft"), page.mscolor1, page.mscolor2, htr_getzindex(document.getElementById("pgktop"))-1); - } + // Compute layout data. + const offs = $(area.layer).offset(); + const x = area.x + offs.left; + const y = area.y + offs.top; + const w = area.width; + const h = area.height; + + if (cx__capabilities.Dom0NS) + { + pg_mkbox(l, + x, y, w, h, 1, + document.layers.pgtop, + document.layers.pgbtm, + document.layers.pgrgt, + document.layers.pglft, + page.mscolor1, page.mscolor2, + document.layers.pgktop.zIndex - 1 + ); + } + else if (cx__capabilities.Dom1HTML) + { + pg_mkbox(l, + x, y, w, h, 1, + document.getElementById("pgtop"), + document.getElementById("pgbtm"), + document.getElementById("pgrgt"), + document.getElementById("pglft"), + page.mscolor1, page.mscolor2, + htr_getzindex(document.getElementById("pgktop")) - 1 + ); + } + }; + + // Initial update. + update_box(pg_curarea); + + // Responsive updates. + const area = pg_curarea; // Save value so we can create a closure below. + resize_handlers.mouse_focus = () => update_box(area); } } } + if (!a) delete resize_handlers.mouse_focus; } function pg_removekbdfocus(p) @@ -1975,16 +2008,30 @@ function pg_removekbdfocus(p) pg_mkbox(null,0,0,0,0, 1, document.getElementById("pgktop"),document.getElementById("pgkbtm"),document.getElementById("pgkrgt"),document.getElementById("pgklft"), page.kbcolor1, page.kbcolor2, pg_get_style(document.getElementById("pgtop"), 'zIndex')+100); } } + + // Clear resize handling. + delete resize_handlers.mouse_focus; + delete resize_handlers.data_focus; + delete resize_handlers.kbd_focus; + return true; } function pg_setdatafocus(a) { + if (!a) return false; + var x = getPageX(a.layer)+a.x; var y = getPageY(a.layer)+a.y; var w = a.width; var h = a.height; var l = a.layer; + + // Setup resize handling. + resize_handlers.data_focus = () => { + // Recall function to update values. + pg_setdatafocus(a); + }; // hide old data focus box if (l.pg_dttop != null) @@ -2037,6 +2084,8 @@ function pg_setdatafocus(a) function pg_setkbdfocus(l, a, xo, yo) { + if (!l) return false; + var from_kbd = false; if (xo == null && yo == null) { @@ -2068,6 +2117,12 @@ function pg_setkbdfocus(l, a, xo, yo) pg_curkbdarea = a; pg_curkbdlayer = l; + // Setup resize handling. + resize_handlers.kbd_focus = () => { + // Recall function to update values. + pg_setkbdfocus(l, a, xo, yo); + }; + if (pg_curkbdlayer && pg_curkbdlayer.getfocushandler) { v=pg_curkbdlayer.getfocushandler(xo,yo,a.layer,a.cls,a.name,a,from_kbd); @@ -3040,9 +3095,12 @@ function pg_check_resize(l) { if (wgtrGetServerProperty(l, "height") != $(l).height()) { - if (wgtrGetParent(l).childresize) + const parent = wgtrGetParent(l); + if (parent.childresize) { - var geom = wgtrGetParent(l).childresize(l, wgtrGetServerProperty(l, "width"), wgtrGetServerProperty(l, "height"), $(l).width(), $(l).height()); + const width = wgtrGetServerProperty(l, "width"); + const height = wgtrGetServerProperty(l, "height"); + const geom = parent.childresize(l, width, height, $(l).width(), $(l).height()); if (geom) { wgtrSetServerProperty(l, "height", geom.height); From 2f3426fc58d75f9744811368c9c7381975395b9a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:40:34 -0700 Subject: [PATCH 051/101] Cleanup unused comments. --- centrallix-os/sys/js/htdrv_osrc.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_osrc.js b/centrallix-os/sys/js/htdrv_osrc.js index f256cd70e..7e3cdc89f 100644 --- a/centrallix-os/sys/js/htdrv_osrc.js +++ b/centrallix-os/sys/js/htdrv_osrc.js @@ -2330,8 +2330,6 @@ function osrc_open_query_startat() else this.querysize = this.replicasize; this.query_ended = false; - //this.LogStatus(); - //console.log('OSRC ' + this.__WgtrName + ': startat ' + this.startat + ', rowcount ' + this.querysize); this.DoRequest('multiquery', '/', {ls__startat:this.startat, ls__autoclose_sr:1, ls__autofetch:1, ls__objmode:0, ls__notify:this.request_updates, ls__rowcount:this.querysize, ls__sql:this.query, ls__sqlparam:this.EncodeParams()}, osrc_get_qid_startat); } From 88774b30d7682950ef75a92d0ef36f37abec2eec Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:41:24 -0700 Subject: [PATCH 052/101] Add error messages to the image widget. Add an error when the offset action is used, warning that it breaks responsive design. Add an error when the scale action is used, warning that it breaks responsive design. --- centrallix-os/sys/js/htdrv_image.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/centrallix-os/sys/js/htdrv_image.js b/centrallix-os/sys/js/htdrv_image.js index a4a608022..38d05b6cf 100644 --- a/centrallix-os/sys/js/htdrv_image.js +++ b/centrallix-os/sys/js/htdrv_image.js @@ -146,12 +146,18 @@ function im_set_scale(a, v) function im_action_offset(aparam) { + /** This action is never used anywhere and looks hard to update, so it's deprecated for now. **/ + console.error("The offset action is no longer supported. Using it may break responsive layouts."); + im_set_x.call(this, "xoffset", aparam.X); im_set_y.call(this, "yoffset", aparam.Y); } function im_action_scale(aparam) { + /** This action is never used anywhere and looks hard to update, so it's deprecated for now. **/ + console.error("The scale action is no longer supported. Using it may break responsive layouts."); + im_set_scale.call(this, "scale", aparam.Scale); } From 59891b398f8019840522dddf0f14af36e34c3551 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:58:40 -0700 Subject: [PATCH 053/101] Make table widget back end code responsive. Improve HTML generation C code. Reformat script call to massively improve readability. Reorder attributes to improve code organization. Rename box to scroll thumb for clarity and consistency. Remove code that renamed legacy attributes to their new names (no longer needed). Remove code that wrote unused attributes to the front end. Remove code that read unused attributes which were never used. Remove unused attributes from the httbl_struct struct. Remove unused variables. Add a TODO to investigate some strange looking logic. Add comments with comment anchors for faster code navigation. Add comment explaining where to find more info about comment anchors. Note: This comment will be wrong until the dups branch is merged to add the required info. Clean up code, comments, and formatting. --- centrallix/htmlgen/htdrv_table.c | 565 +++++++++++++++++++------------ 1 file changed, 352 insertions(+), 213 deletions(-) diff --git a/centrallix/htmlgen/htdrv_table.c b/centrallix/htmlgen/htdrv_table.c index 54dc47f41..5876bf658 100644 --- a/centrallix/htmlgen/htdrv_table.c +++ b/centrallix/htmlgen/htdrv_table.c @@ -35,7 +35,7 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: htdrv_table.c */ +/* Module: htdrv_table.c */ /* Author: Greg Beeley (GRB) */ /* Creation: October 29, 1999 */ /* Description: HTML Widget driver for a data-driven table. Has three */ @@ -58,6 +58,10 @@ /* only. Dynamicrow tables use the most client resources. */ /************************************************************************/ +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. + ***/ + #define HTTBL_MAX_COLS (64) @@ -92,12 +96,7 @@ typedef struct typedef struct { char name[64]; - char sbuf[160]; - char tbl_bgnd[128]; char hdr_bgnd[128]; - char row_bgnd1[128]; - char row_bgnd2[128]; - char row_bgndhigh[128]; char colsep_bgnd[128]; char textcolor[64]; char textcolorhighlight[64]; @@ -105,34 +104,25 @@ typedef struct char newrow_bgnd[128]; char newrow_textcolor[64]; char osrc[64]; - char row_border[64]; - char row_shadow_color[64]; - int row_shadow; - int row_shadow_radius; - int row_radius; - int x,y,w,h; + httbl_col* col_infs[HTTBL_MAX_COLS]; + int ncols; int id; + int x,y,w,h; int data_mode; /* 0="rows" or 1="properties" */ - int outer_border; - int inner_border; int inner_padding; - httbl_col* col_infs[HTTBL_MAX_COLS]; - int ncols; int windowsize; int min_rowheight; int max_rowheight; int cellhspacing; int cellvspacing; - int followcurrent; int dragcols; int colsep; int colsep_mode; - int gridinemptyrows; + int grid_in_empty_rows; int allow_selection; int show_selection; int initial_selection; int allow_deselection; - int reverse_order; int overlap_scrollbar; /* scrollbar overlaps with table */ int hide_scrollbar; /* don't show scrollbar at all */ int demand_scrollbar; /* only show scrollbar when needed */ @@ -140,20 +130,11 @@ typedef struct int rowcache_size; /* number of rows the table caches for display */ } httbl_struct; + int httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) { - int colid; - char *ptr; - int i; - pWgtrNode sub_tree; - int subcnt = 0; - char *nptr; - int h; - int first_offset = (t->has_header)?(t->min_rowheight + t->cellvspacing):0; - pWgtrNode children[32]; - int detailcnt; - httbl_col* col; + char* ptr; if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) { @@ -161,107 +142,279 @@ httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) return -1; } - /** STYLE for the layer **/ - htrAddStylesheetItem_va(s,"\t#tbld%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; } \n",t->id,t->x,t->y,(t->overlap_scrollbar)?(t->w):(t->w-18),z+0); - htrAddStylesheetItem_va(s,"\t#tbld%POSscroll { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:18px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",t->id,(t->hide_scrollbar || t->demand_scrollbar)?"hidden":"inherit",t->x+t->w-18,t->y+first_offset,t->h-first_offset,z+0); - htrAddStylesheetItem_va(s,"\t#tbld%POSbox { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:18px; WIDTH:16px; HEIGHT:16px; Z-INDEX:%POS; BORDER: solid 1px; BORDER-COLOR: white gray gray white; }\n",t->id,z+1); + /** Write CSS for the table base element. **/ + const int content_width = (t->overlap_scrollbar) ? (t->w) : (t->w - 18); + htrAddStylesheetItem_va(s, + "\t#tbld%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, + ht_flex_x(t->x, tree), + ht_flex_y(t->y, tree), + ht_flex_w(content_width, tree), + ht_flex_h(t->h, tree), + z + 0 + ); + + /** Write CSS for the table scrollbar. **/ + const int row_start_y = (t->has_header) ? (t->min_rowheight + t->cellvspacing) : 0; + htrAddStylesheetItem_va(s, + "\t#tbld%POSscroll { " + "position:absolute; " + "visibility:%STR; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:18px; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, + (t->hide_scrollbar || t->demand_scrollbar) ? "hidden" : "inherit", /** TODO: Greg - This logic looks fishy. Why does `demand_scrollbar = true` hide the scrollbar?? **/ + ht_flex(t->x + t->w - 18, ht_get_parent_w(tree), ht_get_fl_x(tree) + ht_get_fl_w(tree)), + ht_flex_y(t->y + row_start_y, tree), + ht_flex_h(t->h - row_start_y, tree), + z + 0 + ); + + /** Write CSS for the table scroll thumb. **/ + htrAddStylesheetItem_va(s, + "\t#tbld%POSthumb { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:18px; " + "width:16px; " + "height:16px; " + "z-index:%POS; " + "border:solid 1px; " + "border-color:white gray gray white; " + "}\n", + t->id, + z + 1 + ); + + /** Link to the table base object. **/ + htrAddWgtrObjLinkage_va(s, tree, "tbld%POSbase", t->id); + /** Add globals for scripts. **/ htrAddScriptGlobal(s,"tbld_current","null",0); htrAddScriptGlobal(s,"tbldb_current","null",0); htrAddScriptGlobal(s,"tbldx_current","null",0); htrAddScriptGlobal(s,"tbldb_start","null",0); htrAddScriptGlobal(s,"tbldbdbl_current","null",0); + /** Include scripts. **/ htrAddScriptInclude(s, "/sys/js/htdrv_table.js", 0); htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - - htrAddWgtrObjLinkage_va(s, tree, "tbld%POSpane",t->id); - - htrAddScriptInit_va(s," tbld_init({tablename:'%STR&SYM', table:wgtrGetNodeRef(ns,\"%STR&SYM\"), scroll:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"tbld%POSscroll\"), boxname:\"tbld%POSbox\", name:\"%STR&SYM\", height:%INT, width:%INT, innerpadding:%INT, innerborder:%INT, windowsize:%INT, min_rowheight:%INT, max_rowheight:%INT, cellhspacing:%INT, cellvspacing:%INT, textcolor:\"%STR&JSSTR\", textcolorhighlight:\"%STR&JSSTR\", titlecolor:\"%STR&JSSTR\", rowbgnd1:\"%STR&JSSTR\", rowbgnd2:\"%STR&JSSTR\", rowbgndhigh:\"%STR&JSSTR\", hdrbgnd:\"%STR&JSSTR\", followcurrent:%INT, dragcols:%INT, colsep:%INT, colsep_mode:%INT, colsep_bgnd:\"%STR&JSSTR\", gridinemptyrows:%INT, reverse_order:%INT, allow_selection:%INT, show_selection:%INT, initial_selection:%INT, allow_deselection:%INT, overlap_sb:%INT, hide_sb:%INT, demand_sb:%INT, osrc:%['%STR&SYM'%]%[null%], dm:%INT, hdr:%INT, newrow_bgnd:\"%STR&JSSTR\", newrow_textcolor:\"%STR&JSSTR\", rcsize:%INT, cols:[", - t->name,t->name,t->name,t->id,t->id,t->name,t->h, - (t->overlap_scrollbar)?t->w:t->w-18, - t->inner_padding,t->inner_border,t->windowsize,t->min_rowheight, t->max_rowheight, - t->cellhspacing, t->cellvspacing,t->textcolor, - t->textcolorhighlight, t->titlecolor,t->row_bgnd1,t->row_bgnd2, - t->row_bgndhigh,t->hdr_bgnd,t->followcurrent,t->dragcols, - t->colsep, t->colsep_mode, t->colsep_bgnd,t->gridinemptyrows, t->reverse_order, - t->allow_selection, t->show_selection, t->initial_selection, t->allow_deselection, - t->overlap_scrollbar, t->hide_scrollbar, t->demand_scrollbar, - *(t->osrc) != '\0', t->osrc, *(t->osrc) == '\0', - t->data_mode, t->has_header, - t->newrow_bgnd, t->newrow_textcolor, - t->rowcache_size); - for(colid=0;colidncols;colid++) + /** Begin writing the js initialization call. **/ + htrAddScriptInit_va(s, "tbld_init({"); + + /** Write identification data. **/ + const int has_osrc = (*(t->osrc) != '\0'); + htrAddScriptInit_va(s, + "name:'%STR&SYM', " + "table:wgtrGetNodeRef(ns, '%STR&SYM'), " + "scroll:htr_subel(" + "wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), " + "'tbld%POSscroll'" + "), " + "osrc:%['%STR&SYM'%]%[null%], ", + t->name, t->name, t->name, t->id, + has_osrc, t->osrc, !has_osrc + ); + + /** Write layout data. **/ + htrAddScriptInit_va(s, + "height:%INT, " + "width:%INT, " + "innerpadding:%INT, " + "min_rowheight:%INT, " + "max_rowheight:%INT, " + "cellhspacing:%INT, " + "cellvspacing:%INT, ", + t->h, content_width, + t->inner_padding, + t->min_rowheight, t->max_rowheight, + t->cellhspacing, t->cellvspacing + ); + + /** Write selection data. **/ + htrAddScriptInit_va(s, + "allow_selection:%INT, " + "show_selection:%INT, " + "initial_selection:%INT, " + "allow_deselection:%INT, ", + t->allow_selection, + t->show_selection, + t->initial_selection, + t->allow_deselection + ); + + /** Write scrollbar data. **/ + htrAddScriptInit_va(s, + "thumb_name:'tbld%POSthumb', " + "demand_sb:%INT, ", + t->id, + t->demand_scrollbar + ); + + /** Write theme data (colors, backgrounds, etc.). **/ + htrAddScriptInit_va(s, + "textcolor:'%STR&JSSTR', " + "textcolorhighlight:'%STR&JSSTR', " + "titlecolor:'%STR&JSSTR', " + "colsep_bgnd:'%STR&JSSTR', " + "hdrbgnd:'%STR&JSSTR', " + "newrow_bgnd:'%STR&JSSTR', " + "newrow_textcolor:'%STR&JSSTR', ", + t->textcolor, t->textcolorhighlight, + t->titlecolor, t->colsep_bgnd, t->hdr_bgnd, + t->newrow_bgnd, t->newrow_textcolor + ); + + /** Write general row data. **/ + htrAddScriptInit_va(s, + "dm:%INT, " + "hdr:%INT, " + "grid_in_empty_rows:%INT, " + "windowsize:%INT, " + "rcsize:%INT, ", + t->data_mode, + t->has_header, + t->grid_in_empty_rows, + t->windowsize, + t->rowcache_size + ); + + /** Write general column data. **/ + htrAddScriptInit_va(s, + "dragcols:%INT, " + "colsep:%INT, " + "colsep_mode:%INT, ", + t->dragcols, t->colsep, t->colsep_mode + ); + + /** Write the cols array with data for each column. **/ + /** ANCHOR[id=table-column] **/ + htrAddScriptInit_va(s, "cols:["); + for (int colid = 0; colid < t->ncols; colid++) { - col = t->col_infs[colid]; - htrAddScriptInit_va(s,"{name:\"%STR&JSSTR\",ns:\"%STR&JSSTR\",fieldname:\"%STR&JSSTR\",sort_fieldname:\"%STR&JSSTR\",title:\"%STR&JSSTR\",width:%INT,type:\"%STR&JSSTR\",group:%POS,align:\"%STR&JSSTR\",wrap:\"%STR&JSSTR\",caption_fieldname:\"%STR&JSSTR\",caption_textcolor:\"%STR&JSSTR\",image_maxwidth:%POS,image_maxheight:%POS},", - col->wname, - col->wnamespace, - col->fieldname, - col->sort_fieldname, - col->title, - col->width, - col->type, - col->group[0]?1:0, - col->align, - col->wrap, - col->caption_fieldname, - col->caption_textcolor, - col->image_maxwidth, - col->image_maxheight - ); + httbl_col* col = t->col_infs[colid]; + htrAddScriptInit_va(s, + "{ " + "name:'%STR&JSSTR', " + "ns:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "sort_fieldname:'%STR&JSSTR', " + "title:'%STR&JSSTR', " + "width:%INT, " + "type:'%STR&JSSTR', " + "group:%POS, " + "align:'%STR&JSSTR', " + "wrap:'%STR&JSSTR', " + "caption_fieldname:'%STR&JSSTR', " + "caption_textcolor:'%STR&JSSTR', " + "image_maxwidth:%POS, " + "image_maxheight:%POS, " + " }, ", + col->wname, + col->wnamespace, + col->fieldname, + col->sort_fieldname, + col->title, + col->width, + col->type, + col->group[0] ? 1 : 0, + col->align, + col->wrap, + col->caption_fieldname, + col->caption_textcolor, + col->image_maxwidth, + col->image_maxheight + ); } + /** Null terminate the array, and finish writing the init call. **/ htrAddScriptInit(s,"null]});\n"); - htrAddBodyItem_va(s,"
\n",t->id); + /** Write HTML for the table base container. **/ + htrAddBodyItem_va(s,"
\n",t->id); - detailcnt = wgtrGetMatchingChildList(tree, "widget/table-row-detail", children, sizeof(children)/sizeof(pWgtrNode)); - //for (i=0;iChildren));i++) - for (i=0;iChildren), i); - // + pWgtrNode sub_tree = children[i]; + + /** Only affect table-row-detail widgets. **/ wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING,POD(&ptr)); - wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING,POD(&nptr)); - if (strcmp(ptr, "widget/table-row-detail") == 0) { htrCheckNSTransition(s, tree, sub_tree); - if (wgtrGetPropertyValue(sub_tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = t->min_rowheight; - htrAddStylesheetItem_va(s,"\t#tbld%POSsub%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; } \n", - t->id, ++subcnt, t->w-(t->demand_scrollbar?0:18), h, z+1); + /** Write CSS for the table row detail. **/ + int h; + if (wgtrGetPropertyValue(sub_tree, "height", DATA_T_INTEGER, POD(&h)) != 0) h = t->min_rowheight; + htrAddStylesheetItem_va(s, + "\t#tbld%POSsub%POS { " + "position:absolute; " + "visibility:hidden; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, ++subcnt, + ht_flex_w(t->w - (t->demand_scrollbar ? 0 : 18), tree), + ht_flex_h(h, tree), + z + 1 + ); + + /** Write HTML (including subwidgets). **/ htrAddBodyItem_va(s,"
\n", t->id, subcnt); htrRenderSubwidgets(s, sub_tree, z+2); htrAddBodyItem(s,"
\n"); + + /** Add linkage. **/ htrAddWgtrObjLinkage_va(s, sub_tree, "tbld%POSsub%POS", t->id, subcnt); - htrCheckAddExpression(s, sub_tree, nptr, "display_for"); + + /** Add 'display_for'. **/ + wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING,POD(&ptr)); + htrCheckAddExpression(s, sub_tree, ptr, "display_for"); htrCheckNSTransitionReturn(s, tree, sub_tree); } - //else if (strcmp(ptr,"widget/table-column") != 0) //got columns earlier - //{ - //htrRenderWidget(s, sub_tree, z+3); - //} } htrRenderSubwidgets(s, tree, z+2); + /** Close the base container. **/ htrAddBodyItem(s,"
\n"); - /** HTML body
element for the scrollbar layer. **/ - htrAddBodyItem_va(s,"
\n",t->id); - htrAddBodyItem(s,"\n"); - htrAddBodyItem(s,"\n"); - htrAddBodyItem_va(s,"\n", t->id, t->h-2*18-first_offset); - htrAddBodyItem(s,"\n"); - htrAddBodyItem(s,"
\n"); - /*htrAddBodyItem_va(s,"
\n",t->id);*/ - htrAddBodyItem_va(s,"
\n",t->id); - htrAddBodyItem(s,"
\n"); - + /** Write HTML for the scrollbar. **/ + htrAddBodyItem_va(s, + "
\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "
\n" + "
\n", + t->id, t->id, ht_flex_h(t->h - row_start_y - 2*18, tree), t->id + ); + + /** Register event handlers. **/ htrAddEventHandlerFunction(s,"document","MOUSEOVER","tbld","tbld_mouseover"); htrAddEventHandlerFunction(s,"document","MOUSEOUT","tbld","tbld_mouseout"); htrAddEventHandlerFunction(s,"document","MOUSEDOWN","tbld","tbld_mousedown"); @@ -288,14 +441,18 @@ httblRender(pHtSession s, pWgtrNode tree, int z) { pWgtrNode sub_tree; char* ptr; - char* nptr; int n, i; httbl_struct* t; int rval; pWgtrNode children[HTTBL_MAX_COLS]; httbl_col* col; - /** Don't try to render table-column, etc. We do that elsewhere **/ + /*** Only render "widget/table". Other widgets (e.g. table-column or + *** table-row-detail) are rendered elsewhere (see links below). + *** + *** LINK #table-column + *** LINK #table-row-detail + ***/ wgtrGetPropertyValue(tree,"outer_type",DATA_T_STRING,POD(&ptr)); if (strcmp(ptr, "widget/table") != 0) return 0; @@ -304,154 +461,131 @@ httblRender(pHtSession s, pWgtrNode tree, int z) if (!t) return -1; memset(t, 0, sizeof(httbl_struct)); - t->x=-1; - t->y=-1; - - /** Get an id for thit. **/ + /** Get an id. **/ t->id = (HTTBL.idcnt++); - /** Backwards compat for the time being **/ - wgtrRenameProperty(tree, "row_bgcolor1", "row1_bgcolor"); - wgtrRenameProperty(tree, "row_background1", "row1_background"); - wgtrRenameProperty(tree, "row_bgcolor2", "row2_bgcolor"); - wgtrRenameProperty(tree, "row_background2", "row2_background"); - wgtrRenameProperty(tree, "row_bgcolorhighlight", "rowhighlight_bgcolor"); - wgtrRenameProperty(tree, "row_backgroundhighlight", "rowhighlight_background"); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&(t->x))) != 0) t->x = -1; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&(t->y))) != 0) t->y = -1; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&(t->w))) != 0) t->w = -1; - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&(t->h))) != 0) + /** Get name. **/ + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) { - mssError(1,"HTTBL","'height' property is required"); + nmFree(t, sizeof(httbl_struct)); return -1; } - if (wgtrGetPropertyValue(tree,"windowsize",DATA_T_INTEGER,POD(&(t->windowsize))) != 0) t->windowsize = -1; - if (wgtrGetPropertyValue(tree,"rowheight",DATA_T_INTEGER,POD(&n)) == 0) + strtcpy(t->name, ptr, sizeof(t->name)); + + /** Get object source path. **/ + if (wgtrGetPropertyValue(tree, "objectsource", DATA_T_STRING, POD(&ptr)) != 0) + strcpy(t->osrc, ""); + else + strtcpy(t->osrc, ptr, sizeof(t->osrc)); + + /** Get the location, size, and layout data. **/ + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&(t->x))) != 0) t->x = -1; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&(t->y))) != 0) t->y = -1; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&(t->w))) != 0) t->w = -1; + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&(t->h))) != 0) { - t->min_rowheight = t->max_rowheight = n; + mssError(1,"HTTBL","'height' property is required"); + return -1; } - else + if (wgtrGetPropertyValue(tree, "rowheight", DATA_T_INTEGER, POD(&n)) != 0) { t->min_rowheight = s->ClientInfo->ParagraphHeight + 2; t->max_rowheight = -1; } - wgtrGetPropertyValue(tree,"min_rowheight",DATA_T_INTEGER,POD(&(t->min_rowheight))); - wgtrGetPropertyValue(tree,"max_rowheight",DATA_T_INTEGER,POD(&(t->max_rowheight))); - if (wgtrGetPropertyValue(tree,"cellhspacing",DATA_T_INTEGER,POD(&(t->cellhspacing))) != 0) t->cellhspacing = 1; - if (wgtrGetPropertyValue(tree,"cellvspacing",DATA_T_INTEGER,POD(&(t->cellvspacing))) != 0) t->cellvspacing = 1; - - if (wgtrGetPropertyValue(tree,"colsep",DATA_T_INTEGER,POD(&(t->colsep))) != 0) t->colsep = 1; - if (wgtrGetPropertyValue(tree,"colsep_mode",DATA_T_STRING,POD(&ptr)) == 0) + else { - if (!strcasecmp(ptr, "full")) - t->colsep_mode = 0; - else if (!strcasecmp(ptr, "header")) - t->colsep_mode = 1; + t->min_rowheight = t->max_rowheight = n; } - - if (wgtrGetPropertyValue(tree,"rowcache_size",DATA_T_INTEGER,POD(&(t->rowcache_size))) != 0) t->rowcache_size = 0; - - t->dragcols = htrGetBoolean(tree, "dragcols", 1); - t->gridinemptyrows = htrGetBoolean(tree, "gridinemptyrows", 1); + if (wgtrGetPropertyValue(tree, "inner_padding", DATA_T_INTEGER, POD(&(t->inner_padding))) != 0) t->inner_padding = 0; + if (wgtrGetPropertyValue(tree, "min_rowheight", DATA_T_INTEGER, POD(&(t->min_rowheight))) != 0); /* Keep value from above. */ + if (wgtrGetPropertyValue(tree, "max_rowheight", DATA_T_INTEGER, POD(&(t->max_rowheight))) != 0); /* Keep value from above. */ + if (wgtrGetPropertyValue(tree, "cellhspacing", DATA_T_INTEGER, POD(&(t->cellhspacing))) != 0) t->cellhspacing = 1; + if (wgtrGetPropertyValue(tree, "cellvspacing", DATA_T_INTEGER, POD(&(t->cellvspacing))) != 0) t->cellvspacing = 1; + + /** Get selection data. **/ t->allow_selection = htrGetBoolean(tree, "allow_selection", 1); t->show_selection = htrGetBoolean(tree, "show_selection", 1); - if (wgtrGetPropertyType(tree, "initial_selection") == DATA_T_STRING && wgtrGetPropertyValue(tree,"initial_selection",DATA_T_STRING,POD(&ptr)) == 0 && !strcasecmp(ptr,"noexpand")) + if (wgtrGetPropertyType(tree, "initial_selection") == DATA_T_STRING + && wgtrGetPropertyValue(tree, "initial_selection", DATA_T_STRING, POD(&ptr)) == 0 + && strcasecmp(ptr, "noexpand") == 0) t->initial_selection = 2; else t->initial_selection = htrGetBoolean(tree, "initial_selection", 1); - t->allow_deselection = htrGetBoolean(tree, "allow_deselection", t->initial_selection?0:1); - t->reverse_order = htrGetBoolean(tree, "reverse_order", 0); + t->allow_deselection = htrGetBoolean(tree, "allow_deselection", (t->initial_selection) ? 0 : 1); + /** Get scrollbar data. **/ t->overlap_scrollbar = htrGetBoolean(tree, "overlap_scrollbar", 0); t->hide_scrollbar = htrGetBoolean(tree, "hide_scrollbar", 0); t->demand_scrollbar = htrGetBoolean(tree, "demand_scrollbar", 0); - t->has_header = htrGetBoolean(tree, "titlebar", 1); - - /** Which data mode to use? **/ - if (wgtrGetPropertyValue(tree,"data_mode", DATA_T_STRING, POD(&ptr)) == 0) + + /** Get theme data (colors, backgrounds, etc.). **/ + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->textcolor, ptr, sizeof(t->textcolor)); + if (wgtrGetPropertyValue(tree, "textcolorhighlight", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->textcolorhighlight, ptr, sizeof(t->textcolorhighlight)); + if (wgtrGetPropertyValue(tree, "titlecolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->titlecolor, ptr, sizeof(t->titlecolor)); + if (!*t->titlecolor) strcpy(t->titlecolor, t->textcolor); + htrGetBackground(tree, "colsep", !s->Capabilities.Dom0NS, t->colsep_bgnd, sizeof(t->colsep_bgnd)); + htrGetBackground(tree, "hdr", !s->Capabilities.Dom0NS, t->hdr_bgnd, sizeof(t->hdr_bgnd)); + htrGetBackground(tree, "newrow", !s->Capabilities.Dom0NS, t->newrow_bgnd, sizeof(t->newrow_bgnd)); + if (wgtrGetPropertyValue(tree, "textcolornew", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->newrow_textcolor, ptr, sizeof(t->newrow_textcolor)); + + /** Get general row data. **/ + if (wgtrGetPropertyValue(tree, "data_mode", DATA_T_STRING, POD(&ptr)) != 0) + t->data_mode = 0; + else { - if (!strcmp(ptr, "rows")) - t->data_mode = 0; - else if (!strcmp(ptr, "properties")) - t->data_mode = 1; + if (strcasecmp(ptr, "rows") == 0) t->data_mode = 0; + else if (strcasecmp(ptr, "properties") == 0) t->data_mode = 1; + else + { + mssError(1, "TBL", "Invalid value for attribute 'data_mode': %s", ptr); + return -1; + } } + t->has_header = htrGetBoolean(tree, "titlebar", 1); /* Whether to render a header row. */ + t->grid_in_empty_rows = htrGetBoolean(tree, "gridinemptyrows", 1); /* Whether to show the grid in empty rows. */ + if (wgtrGetPropertyValue(tree, "windowsize", DATA_T_INTEGER, POD(&(t->windowsize))) != 0) t->windowsize = -1; + if (wgtrGetPropertyValue(tree, "rowcache_size", DATA_T_INTEGER, POD(&(t->rowcache_size))) != 0) t->rowcache_size = 0; - /** Should we follow the current record around? **/ - t->followcurrent = htrGetBoolean(tree, "followcurrent", 1); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) + /** Get general column data. **/ + t->dragcols = htrGetBoolean(tree, "dragcols", 1); + if (wgtrGetPropertyValue(tree, "colsep", DATA_T_INTEGER, POD(&(t->colsep))) != 0) t->colsep = 1; + if (wgtrGetPropertyValue(tree, "colsep_mode", DATA_T_STRING, POD(&ptr)) != 0) + t->colsep_mode = 0; + else { - nmFree(t, sizeof(httbl_struct)); - return -1; + if (strcasecmp(ptr, "full") == 0) t->colsep_mode = 0; + else if (strcasecmp(ptr, "header") == 0) t->colsep_mode = 1; + else + { + mssError(1, "TBL", "Invalid value for attribute 'colsep_mode': %s", ptr); + return -1; + } } - strtcpy(t->name,ptr,sizeof(t->name)); - - if (wgtrGetPropertyValue(tree,"objectsource",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->osrc,ptr,sizeof(t->osrc)); - else - strcpy(t->osrc,""); - - /** Get background color/image for table header **/ - htrGetBackground(tree, NULL, !s->Capabilities.Dom0NS, t->tbl_bgnd, sizeof(t->tbl_bgnd)); - - /** Get background color/image for header row **/ - htrGetBackground(tree, "hdr", !s->Capabilities.Dom0NS, t->hdr_bgnd, sizeof(t->hdr_bgnd)); - - /** Get background color/image for rows **/ - htrGetBackground(tree, "row1", !s->Capabilities.Dom0NS, t->row_bgnd1, sizeof(t->row_bgnd1)); - htrGetBackground(tree, "row2", !s->Capabilities.Dom0NS, t->row_bgnd2, sizeof(t->row_bgnd2)); - htrGetBackground(tree, "rowhighlight", !s->Capabilities.Dom0NS, t->row_bgndhigh, sizeof(t->row_bgndhigh)); - htrGetBackground(tree, "colsep", !s->Capabilities.Dom0NS, t->colsep_bgnd, sizeof(t->colsep_bgnd)); - htrGetBackground(tree, "newrow", !s->Capabilities.Dom0NS, t->newrow_bgnd, sizeof(t->newrow_bgnd)); - - /** Get borders and padding information **/ - wgtrGetPropertyValue(tree,"outer_border",DATA_T_INTEGER,POD(&(t->outer_border))); - wgtrGetPropertyValue(tree,"inner_border",DATA_T_INTEGER,POD(&(t->inner_border))); - wgtrGetPropertyValue(tree,"inner_padding",DATA_T_INTEGER,POD(&(t->inner_padding))); - - /** Row decorations **/ - wgtrGetPropertyValue(tree, "row_border_color", DATA_T_STRING, POD(&t->row_border)); - wgtrGetPropertyValue(tree, "row_shadow_color", DATA_T_STRING, POD(&t->row_shadow_color)); - wgtrGetPropertyValue(tree, "row_shadow_offset", DATA_T_INTEGER, POD(&t->row_shadow)); - wgtrGetPropertyValue(tree, "row_shadow_radius", DATA_T_INTEGER, POD(&t->row_shadow_radius)); - wgtrGetPropertyValue(tree, "row_border_radius", DATA_T_INTEGER, POD(&t->row_radius)); - - /** Text color information **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->textcolor,ptr,sizeof(t->textcolor)); - - /** Text color information **/ - if (wgtrGetPropertyValue(tree,"textcolorhighlight",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->textcolorhighlight,ptr,sizeof(t->textcolorhighlight)); - /** Text color information for "new row" in process of being created **/ - if (wgtrGetPropertyValue(tree,"textcolornew",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->newrow_textcolor,ptr,sizeof(t->newrow_textcolor)); - - /** Title text color information **/ - if (wgtrGetPropertyValue(tree,"titlecolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->titlecolor,ptr,sizeof(t->titlecolor)); - if (!*t->titlecolor) strcpy(t->titlecolor,t->textcolor); - - /** Get column data **/ + /** Get specific column data for each column. **/ t->ncols = wgtrGetMatchingChildList(tree, "widget/table-column", children, sizeof(children)/sizeof(pWgtrNode)); for (i=0;incols;i++) { sub_tree = children[i]; + + /** Only read table-column widgets. **/ wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING,POD(&ptr)); - wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING,POD(&nptr)); - if (!strcmp(ptr,"widget/table-column") != 0) + if (strcmp(ptr, "widget/table-column") == 0) { + /** Allocate space to store the column data. **/ col = (httbl_col*)nmMalloc(sizeof(httbl_col)); memset(col, 0, sizeof(*col)); + + /** Basic column info. **/ t->col_infs[i] = col; strtcpy(col->wname, wgtrGetName(sub_tree), sizeof(col->wname)); strtcpy(col->wnamespace, wgtrGetNamespace(sub_tree), sizeof(col->wnamespace)); - /** no layer associated with this guy **/ + /** The object system doesn't need to render this widget (we will do that). **/ sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; /** Get column properties **/ @@ -470,8 +604,9 @@ httblRender(pHtSession s, pWgtrNode tree, int z) strtcpy(col->title, ptr, sizeof(col->title)); else strtcpy(col->title, col->fieldname, sizeof(col->title)); - htrCheckAddExpression(s, sub_tree, nptr, "title"); - htrCheckAddExpression(s, sub_tree, nptr, "visible"); + wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING, POD(&ptr)); + htrCheckAddExpression(s, sub_tree, ptr, "title"); + htrCheckAddExpression(s, sub_tree, ptr, "visible"); if (wgtrGetPropertyValue(sub_tree, "align", DATA_T_STRING,POD(&ptr)) == 0) strtcpy(col->align, ptr, sizeof(col->align)); else @@ -489,7 +624,10 @@ httblRender(pHtSession s, pWgtrNode tree, int z) } } + /** Render the table. **/ rval = httblRenderDynamic(s, tree, z, t); + + /** Clean up. **/ for(i=0;incols;i++) nmFree(t->col_infs[i], sizeof(httbl_col)); nmFree(t, sizeof(httbl_struct)); @@ -505,25 +643,26 @@ httblInitialize() { pHtDriver drv; - /** Allocate the driver **/ + /** Allocate the driver struct. **/ drv = htrAllocDriver(); if (!drv) return -1; - /** Fill in the structure. **/ + /** Initialize driver values. **/ strcpy(drv->Name,"DHTML DataTable Driver"); strcpy(drv->WidgetName,"table"); drv->Render = httblRender; xaAddItem(&(drv->PseudoTypes), "table-column"); xaAddItem(&(drv->PseudoTypes), "table-row-detail"); + /** Add driver events. **/ htrAddEvent(drv,"Click"); htrAddEvent(drv,"DblClick"); - /** Register. **/ + /** Register the driver, with dhtml support. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); + /** Initialize the ID counter. **/ HTTBL.idcnt = 0; return 0; From b759e9d9828305a7ed0aeb68cbd065629158f14a Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Mon, 9 Feb 2026 12:58:50 -0700 Subject: [PATCH 054/101] Make table widget front end code responsive (I think). Add a resize observer to reflow widths and update the rows, scroll thumb, and no data message when the page was resized. Add code to allow changing column width using any row (not just the header row). Rewrite tbld_change_width() to be better in almost every way (might fix some bugs). Rewrite tbl_reflow_width() to be far easier to read (might fix some bugs). Rewrite tbl_apply_row_geom() to be faster and better in general (might fix some bugs). Refactor no data message handling to reduce duplicate code. Rename box to scroll thumb for clarity and consistency. Remove code that stored attributes in the table even though they were never used. Improve code readability by using Math.clamp(). Improve code readability by using some JS best practices. Improve code readability by using the ?. syntax. Reorder some initialization code for more readable grouping. Revise comments to make them more useful. Add a small amount of doc comments. Add some useful comments. Clean up unused comments. Clean up unused logging. Clean up unused comments about unused logging. --- centrallix-os/sys/js/htdrv_table.js | 466 +++++++++++++++------------- 1 file changed, 251 insertions(+), 215 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_table.js b/centrallix-os/sys/js/htdrv_table.js index 19c5c7d5e..328870cf4 100644 --- a/centrallix-os/sys/js/htdrv_table.js +++ b/centrallix-os/sys/js/htdrv_table.js @@ -12,6 +12,31 @@ window.tbld_touches = []; +const tbld_resize_observer = new ResizeObserver((entries) => entries.forEach(({ + contentRect: { width, height }, + target: table +}) => + { + // Set the new size. + table.param_width = width; + table.param_height = height; + + // Update scroll thumb and reflow the columns. + table.UpdateThumb(false); + table.ReflowWidth(width); + table.UpdateNDM($(table).children('#ndm')); + + // Resize all rows. + const { rows } = table; + for (let i = (table.has_header) ? 0 : rows.first; i <= rows.last; i++) + { + const row = rows[i]; + if (!row) continue; + $(row).css({ width: (row.w = width) }); + } + } +)); + function tbld_log_status() { var rowstr = ''; @@ -60,27 +85,33 @@ function tbld_format_cell(cell, color) var bartext = wgtrGetServerProperty(wgtrFindDescendent(this, this.cols[cell.colnum].name, this.cols[cell.colnum].ns), 'bar_textcolor'); if (!bartext) bartext = 'black'; bartext = String(bartext).replace(/[^a-z0-9A-Z#]/g, ""); - var actpct = '' + (100 * ((val < 0)?0:((val > 1)?1:val))) + '%'; - actpct = String(actpct).replace(/[^0-9.%]/g, ""); - var pct = '' + (Math.round(val * 100 / roundto) * roundto) + '%'; + const width_percent = ('' + (100 * Math.clamp(0, val, 1)) + '%').replace(/[^0-9.%]/g, ""); + const percent = '' + (Math.round(val * 100 / roundto) * roundto) + '%'; if (val >= 0.5) { - innertxt = pct + ' '; + innertxt = percent + ' '; outertxt = ''; } else { innertxt = ' '; - outertxt = ' ' + pct; + outertxt = ' ' + percent; } - txt = '
' + - '
' + - htutil_encode(innertxt) + - '
' + - (outertxt?('' + - htutil_encode(outertxt) + - ''):'') + - '
'; + txt = + '
' + + '
' + + htutil_encode(innertxt) + + '
' + + ((outertxt) ? ('= min) - { - ndm.hide(); - } - else - { - ndm.show(); - ndm.text(wgtrGetServerProperty(this,"nodata_message")); - ndm.css({"top":((this.param_height - ndm.height())/2) + "px", "color":wgtrGetServerProperty(this,"nodata_message_textcolor") }); - } + // Redraw the no data message. + var $ndm = $(this).children('#ndm'); + if (max >= min) $ndm.hide(); + else this.UpdateNDM($ndm); // (re)draw the loaded records var selected_position_changed = false; @@ -566,19 +588,12 @@ function tbld_get_selected_geom() return { x:$(obj).offset().left, y:$(obj).offset().top, width:$(obj).width(), height:$(obj).height() }; } - -function tbld_css_height(element, seth) - { - if (seth == null) - { - return parseFloat($(element).css("height")); - } - else - { - $(element).css("height",seth + "px"); - } - } - +function tbld_get_height(node) { + return parseFloat($(node).css("height")); +} +function tbld_set_height(node, new_height) { + $(node).css("height", new_height + "px"); +} function tbld_update_height(row) { @@ -597,8 +612,8 @@ function tbld_update_height(row) h = this.max_rowheight - this.cellvspacing*2; if (h < this.min_rowheight - this.cellvspacing*2) h = this.min_rowheight - this.cellvspacing*2; - if (tbld_css_height(col) != h) - tbld_css_height(col,h); + if (tbld_get_height(col) != h) + tbld_set_height(col, h); if (h > maxheight) maxheight = h; } @@ -617,10 +632,11 @@ function tbld_update_height(row) } // No change? - if (tbld_css_height(row) == maxheight + this.innerpadding*2) + const new_height = maxheight + this.innerpadding*2; + if (tbld_get_height(row) === new_height) return false; - tbld_css_height(row,maxheight + this.innerpadding*2); + tbld_set_height(row, new_height); return true; } @@ -686,7 +702,6 @@ function tbld_format_row(id, selected, do_new) function tbld_bring_into_view(rownum) { - //this.log.push("tbld_bring_into_view(" + rownum + ")"); this.bring_into_view = null; // Clamp the requested row to the available range @@ -939,10 +954,10 @@ function tbld_detail_showcontainer() } } - +/** @param dw The detail widget DOM node. **/ function tbld_update_detail(dw) { - if (dw.display_for && (this.table.initselect !== 2 || (this.table.initselect == 2 && dw.on_new)) /* 2 = noexpand */ && (!dw.on_new || wgtrGetServerProperty(dw, 'show_on_new', 0))) + if (dw.display_for && ((dw.on_new) ? (this.table.initselect !== 2) : wgtrGetServerProperty(dw, 'show_on_new', 0))) { var found=false; for(var j=0; jl.row.w) - // move = l.row.w - rw - colinfo.xoffset - colinfo.width; - if(colinfo.xoffset+colinfo.width+rw+move<0) - move=0-colinfo.xoffset-rw; - if (colinfo.width + move < 3) - move = 3-colinfo.width; - if(l.resizebdr.xoffset+move<0) - move=0-l.resizebdr.xoffset; - //if(getPageX(l.resizebdr) + t.colsep + t.bdr_width*2 + move >= getPageX(t) + t.param_width) - // move = getPageX(t) + t.param_width - getPageX(l.resizebdr) - t.colsep - t.bdr_width*2; + if (col_info_xoffset + col_info_width + rw + move < 0) + move = 0 - col_info_xoffset - rw; + if (col_info_width + move < 3) + move = 3 - col_info_width; + if (resizebdr && resizebdr.xoffset + move < 0) + move = 0 - resizebdr.xoffset; // Figure how much space on the right of this resize handle we're adjusting, too... - var cols_right = t.colcount - l.colnum - 1; - var adj = []; - var total_right_width = 0; - for(var j=l.colnum+1; j= t.rows.first) - { - if (t.ApplyRowGeom(t.rows[i], l.colnum) && t.min_rowheight != t.max_rowheight) + const updated_rows = []; + const { first, last } = rows; + const inflexible_row_height = (t.min_rowheight === t.max_rowheight); + for (let i = 0; i <= last; i++) + { + if (i < first && i !== 0) continue; + + const rowi = rows[i]; + if (!t.ApplyRowGeom(rowi, colnum) || inflexible_row_height) continue; + + // Need to update height of row? + if (!t.UpdateHeight(rowi)) continue; + + for (let j = i + 1; j <= last; j++) + { + const rowj = rows[j]; + if (rowj.positioned) { - // Need to update height of row? - if (t.UpdateHeight(t.rows[i])) - { - for(var j=i+1; j<=t.rows.last; j++) - { - if (t.rows[j].positioned) - { - t.rows[j].positioned = false; - upd_rows.push(t.rows[j]); - } - } - } + rowj.positioned = false; + updated_rows.push(rowj); } } } - if (upd_rows.length) + + if (updated_rows.length) { - t.PositionRows(upd_rows); + t.PositionRows(updated_rows); t.CheckBottom(); } @@ -1397,40 +1410,34 @@ function tbld_change_width(move, compensate) } -function tbld_reflow_width() +function tbld_reflow_width(new_width) { - if (this.hdrrow) - { - var logstr = 'Before reflow widths:'; - var ttl = 0; - for(var i=0; i 0 || this.dragcols) - new_w -= (this.bdr_width*2 + this.colsep); - $(c).width(new_w); - setRelativeX(c, this.cols[j].xoffset); - if (this.cols[j].wrap != 'no') - change_wrapped_cell = true; + const target_col = target_cols[i], this_col = this_cols[i]; + let new_w = this_col.width; // - this.innerpadding*2; + + if (colsep > 0 || dragcols) new_w -= (bdr_width*2 + colsep); + + $(target_col).width(new_w); + fast_setRelativeX(target_col, this_col.xoffset); + + change_wrapped_cell |= (this_col.wrap != 'no'); } return change_wrapped_cell; } @@ -1604,7 +1615,6 @@ function tbld_remove_row(rowobj) } if (this.rows.firstvis > this.rows.lastvis) { - console.log('TABLE ' + this.__WgtrName + ': resetting firstvis/lastvis to null (firstvis > lastvis)'); this.rows.firstvis = null; this.rows.lastvis = null; } @@ -1687,12 +1697,14 @@ function tbld_display_row(rowobj, rowslot) if (!this.rows.lastvis || this.rows.lastvis < rowslot) this.rows.lastvis = rowslot; } - if (getRelativeY(rowobj) < this.scroll_minheight || this.scroll_minheight == null) - this.scroll_minheight = getRelativeY(rowobj); + const rowY = getRelativeY(rowobj); + if (rowY < this.scroll_minheight || this.scroll_minheight == null) + this.scroll_minheight = rowY; if (rowslot < this.scroll_minrec || this.scroll_minrec == null) this.scroll_minrec = rowslot; - if (rowslot == this.rows.lastosrc || (getRelativeY(rowobj) + $(rowobj).height() + this.cellvspacing*2 > this.scroll_maxheight)) - this.scroll_maxheight = getRelativeY(rowobj) + $(rowobj).height() + this.cellvspacing*2; + const rowHeight = $(rowobj).height(); + if (rowslot == this.rows.lastosrc || (rowY + rowHeight + this.cellvspacing*2 > this.scroll_maxheight)) + this.scroll_maxheight = rowY + rowHeight + this.cellvspacing*2; if (rowslot > this.scroll_maxrec) this.scroll_maxrec = rowslot; } @@ -1843,16 +1855,12 @@ function tbld_osrc_dispatch() case 'ScrollTo': this.osrc_busy = true; this.osrc_last_op = item.type; - //this.log.push("Calling ScrollTo(" + item.start + "," + item.end + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); - //console.log("Calling ScrollTo(" + item.start + "," + item.end + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); this.osrc.ScrollTo(item.start, item.end); break; case 'MoveToRecord': this.osrc_busy = true; this.osrc_last_op = item.type; - //this.log.push("Calling MoveToRecord(" + item.rownum + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); - //console.log("Calling MoveToRecord(" + item.rownum + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); this.osrc.MoveToRecord(item.rownum, this); break; @@ -1934,8 +1942,11 @@ function tbld_init(param) { var t = param.table; var scroll = param.scroll; + + // Initialize table. ifc_init_widget(t); t.table = t; + t.name = param.name; // Debug value. t.param_width = param.width; t.param_height = param.height; t.dragcols = param.dragcols; @@ -1955,12 +1966,15 @@ function tbld_init(param) t.cr = 0; t.is_new = 0; t.rowdivcache = []; - t.followcurrent = param.followcurrent>0?true:false; t.hdr_bgnd = param.hdrbgnd; htr_init_layer(t, t, "tabledynamic"); + + // Initialize scrollbar. t.scrollbar = scroll; htr_init_layer(t.scrollbar, t, "tabledynamic"); t.scrollbar.Click = tbld_bar_click; + + // Initialize scrollbar images. var imgs = pg_images(t.scrollbar); for(var img in imgs) { @@ -1971,29 +1985,23 @@ function tbld_init(param) else if (imgs[img].name == 'd') t.down = imgs[img]; } - t.box=htr_subel(scroll,param.boxname); - htr_init_layer(t.box, t, "tabledynamic"); - t.scrollbar.b=t.box; + + // Initialize scroll thumb. + t.thumb = htr_subel(scroll, param.thumb_name); + htr_init_layer(t.thumb, t, "tabledynamic"); + t.scrollbar.b = t.thumb; + + // Initialize events for scrolling. t.up.Click=tbld_up_click; t.down.Click=tbld_down_click; - t.box.Click = new Function( ); - t.scrollbar.table = t.up.table = t.down.table = t.box.table = t; + t.thumb.Click = new Function(); + t.scrollbar.table = t.up.table = t.down.table = t.thumb.table = t; t.up.subkind='up'; t.down.subkind='down'; - t.box.subkind='box'; + t.thumb.subkind='thumb'; t.scrollbar.subkind='bar'; - /*t.dispatch_queue = {}; - t.dispatch_parallel_max = 1; - t.Dispatch = tbld_dispatch; - t.Request = tbld_request;*/ - t.osrc_request_queue = []; - t.osrc_busy = false; - t.osrc_last_op = null; - //t.log = []; - t.ttf_string = ''; - t.selected_row = null; - t.selected = null; + // Initialize layout data. t.rowheight=param.min_rowheight>0?param.min_rowheight:15; t.min_rowheight = param.min_rowheight; t.max_rowheight = param.max_rowheight; @@ -2004,9 +2012,6 @@ function tbld_init(param) t.textcolorhighlight=param.textcolorhighlight?param.textcolorhighlight:param.textcolor; t.textcolornew=param.newrow_textcolor; t.titlecolor=param.titlecolor; - t.row_bgnd1=param.rowbgnd1?param.rowbgnd1:"bgcolor='white'"; - t.row_bgnd2=param.rowbgnd2?param.rowbgnd2:t.row_bgnd1; - t.row_bgndhigh=param.rowbgndhigh?param.rowbgndhigh:"bgcolor='black'"; t.row_bgndnew=param.newrow_bgnd; t.cols=param.cols; t.colcount=0; @@ -2017,17 +2022,29 @@ function tbld_init(param) else delete t.cols[i]; } - if (param.osrc) - t.osrc = wgtrGetNode(t, param.osrc, "widget/osrc"); - else - t.osrc = wgtrFindContainer(t, "widget/osrc"); - if(!t.osrc || !(t.colcount>0)) + if (t.colcount <= 0) { - alert('table widget requires an objectsource and at least one column'); + alert('The table widget requires at least one column'); return t; } - - // Main table widget methods + + // Initialize ObjectSource values. + t.osrc_request_queue = []; + t.osrc_busy = false; + t.osrc_last_op = null; + t.ttf_string = ''; + t.selected_row = null; + t.selected = null; + t.osrc = (param.osrc) + ? wgtrGetNode(t, param.osrc, "widget/osrc") + : wgtrFindContainer(t, "widget/osrc"); + if (!t.osrc) + { + alert('The table widget requires an ObjectSource'); + return t; + } + + // Bind table widget functions. t.RedrawAll = tbld_redraw_all; t.InstantiateRow = tbld_instantiate_row; t.DisplayRow = tbld_display_row; @@ -2050,7 +2067,7 @@ function tbld_init(param) t.SchedScroll = tbld_sched_scroll; t.CheckBottom = tbld_check_bottom; t.ApplyRowGeom = tbld_apply_row_geom; - t.InitBH = tbld_init_bh; + t.UpdateNDM = tbld_update_ndm; t.OsrcDispatch = tbld_osrc_dispatch; t.OsrcRequest = tbld_osrc_request; t.EndTTF = tbld_end_ttf; @@ -2071,6 +2088,7 @@ function tbld_init(param) t.ObjectModified = tbld_object_modified; t.osrc.Register(t); + // Set the number or records visible in the table at one time. if (param.windowsize > 0) { t.windowsize = param.windowsize; @@ -2090,12 +2108,15 @@ function tbld_init(param) if (t.datamode != 1 && t.windowsize > t.osrc.replicasize) t.windowsize = t.osrc.replicasize; + // Handle header row. t.totalwindowsize = t.windowsize + 1; if (!t.has_header) t.windowsize = t.totalwindowsize; t.firstdatarow = t.has_header?1:0; - // Handle column resizing and columns without widths + /*** Handle columns without widths by assigning a default and resizing other + *** columns proportionally. + ***/ var total_w = 0; for (var i in t.cols) { @@ -2125,11 +2146,10 @@ function tbld_init(param) t.grpby = i; } + // Set some other values. t.maxwindowsize = t.windowsize; t.maxtotalwindowsize = t.totalwindowsize; t.rows = {first:null, last:null, firstvis:null, lastvis:null, lastosrc:null}; - setClipWidth(t, param.width); - setClipHeight(t, param.height); t.subkind='table'; t.bdr_width = (t.colsep > 0)?3:0; t.target_y = null; @@ -2208,6 +2228,7 @@ function tbld_init(param) } } + // Initialize scrollbar values. t.scroll_maxheight = null; t.scroll_maxrec = null; t.scroll_minheight = null; @@ -2220,6 +2241,7 @@ function tbld_init(param) // set working area height and scrollbar size t.UpdateGeom(); + // Initialize the scrollbar. t.scrolldiv = htr_new_layer(t.param_width, t.scrollctr); htr_init_layer(t.scrolldiv, t, "tabledynamic"); t.scrolldiv.subkind = "scrolldiv"; @@ -2238,11 +2260,23 @@ function tbld_init(param) if (window.tbld_mcurrent == undefined) window.tbld_mcurrent = null; + // Handle resizing. + tbld_resize_observer.observe(t); + // No data message - var ndm = document.createElement("div"); - $(ndm).css({"position":"absolute", "width":"100%", "text-align":"center", "left":"0px"}); - $(ndm).attr({"id":"ndm"}); - $(t).append(ndm); + var $ndm = $('
'); + $ndm[0].table = t; + $ndm.text(wgtrGetServerProperty(t, "nodata_message")); + $ndm.css({ + position: 'absolute', + width: '100%', + left: '0px', + textAlign: 'center', + color: wgtrGetServerProperty(t, "nodata_message_textcolor"), + }); + $ndm.show(); + $(t).append($ndm); + t.UpdateNDM($ndm); // Events var ie = t.ifcProbeAdd(ifEvent); @@ -2293,17 +2327,14 @@ function tbld_init(param) } } - t.InitBH(); - return t; } -function tbld_init_bh() +function tbld_update_ndm($ndm) { - var ndm = $(this).children('#ndm'); - ndm.show(); - ndm.text(wgtrGetServerProperty(this,"nodata_message")); - ndm.css({"top":((this.param_height - ndm.height())/2) + "px", "color":wgtrGetServerProperty(this,"nodata_message_textcolor") }); + $ndm.css({ + top: ((this.param_height - $ndm.height()) / 2) + "px", + }); } function tbld_touchstart(e) @@ -2584,7 +2615,9 @@ function tbld_keydown(e) for(var c in row.cols) { var col = row.cols[c]; - if (t.cols[col.colnum].type != 'check' && t.cols[col.colnum].type != 'image' && t.cols[col.colnum].type != 'checkbox') + if (t.cols[col.colnum].type != 'check' && + t.cols[col.colnum].type != 'image' && + t.cols[col.colnum].type != 'checkbox') { if (t.CheckHighlight(col, t.ttf_string)) { @@ -2675,7 +2708,7 @@ function tbld_mousedown(e) ly=ly.cell.row; } } - if (ly.subkind == 'box') + if (ly.subkind == 'thumb') { tbldx_current = ly; tbldx_start = e.pageY; @@ -2828,7 +2861,10 @@ function tbld_mousedown(e) } ly.row.table.osrc.ifcProbe(ifAction).Invoke("OrderObject", {orderobj:neworder}); } - if(ly.subkind=='up' || ly.subkind=='bar' || ly.subkind=='down' || ly.subkind=='box') + if (ly.subkind === 'up' + || ly.subkind === 'bar' + || ly.subkind === 'down' + || ly.subkind === 'thumb') { ly.Click(e); } @@ -2868,9 +2904,9 @@ function tbld_mousemove(e) var t = tbldx_current.table; if (tbldx_tstart + incr < 18) incr = 18 - tbldx_tstart; - if (tbldx_tstart + incr + $(t.box).height() > $(t.scrollbar).height() - 18 - 3) - incr = $(t.scrollbar).height() - 18 - 3 - tbldx_tstart - $(t.box).height(); - setRelativeY(t.box, tbldx_tstart + incr); + if (tbldx_tstart + incr + $(t.thumb).height() > $(t.scrollbar).height() - 18 - 3) + incr = $(t.scrollbar).height() - 18 - 3 - tbldx_tstart - $(t.thumb).height(); + setRelativeY(t.thumb, tbldx_tstart + incr); if (t.thumb_avail > t.thumb_height) { t.SchedScroll((-t.scroll_minheight) - Math.floor((tbldx_tstart + incr - 18)*t.thumb_sh/(t.thumb_avail - t.thumb_height))); @@ -2915,7 +2951,7 @@ function tbld_mouseup(e) if (t.colsep > 0 || t.dragcols) maxw += (t.bdr_width*2 + t.colsep); l.ChangeWidth(maxw-t.cols[l.colnum].width, true); - t.ReflowWidth(); + t.ReflowWidth(t.hdrrow.w); } else { From 9bfb614d6f5bdd525b4743b41d1ea328a1ba334c Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 11 Feb 2026 12:13:08 -0700 Subject: [PATCH 055/101] Improve button layout on radio button panel widgets. Add a spacing attribute to set the space between buttons (with documentation). Reorganize C generation code, greatly reducing unnecessary work and improving code readability and usability. Fix styling failures in htdrv_radiobutton.c. Add comments & clean up. --- centrallix-doc/Widgets/widgets.xml | 4 +- centrallix/htmlgen/htdrv_radiobutton.c | 583 ++++++++++++------------- 2 files changed, 294 insertions(+), 293 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index 0dd4ddc37..1dea58f85 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -3091,7 +3091,9 @@ my_cmp "widget/component-decl" A color, RGB or named, for the panel background. If neither bgcolor nor background transparent. Height, in pixels, of the panel. - + + The maximum height (in pixels) of space allowed between radio buttons on the panel (default: 10px). + An image to be used for the rectangular border drawn around the radio buttons. The color, RGB or named, of the text within the panel. Default: "black". diff --git a/centrallix/htmlgen/htdrv_radiobutton.c b/centrallix/htmlgen/htdrv_radiobutton.c index d5258ed5e..5946e048c 100644 --- a/centrallix/htmlgen/htdrv_radiobutton.c +++ b/centrallix/htmlgen/htdrv_radiobutton.c @@ -44,99 +44,144 @@ /** globals **/ -static struct { - int idcnt; -} HTRB; +static struct + { + int idcnt; + } + HTRB; /** htrbRender - generate the HTML code for the page. **/ -int htrbRender(pHtSession s, pWgtrNode tree, int z) { - char* ptr; - char name[64]; - char title[64]; - char sbuf2[200]; - //char bigbuf[4096]; - char textcolor[32]; - char main_background[128]; - char outline_background[128]; - char form[64]; - pWgtrNode radiobutton_obj, sub_tree; - int x=-1,y=-1,w,h; - int id, i, j; - int is_selected; - int raido_button_count; - char fieldname[32]; - char value[64]; - char label[64]; - - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) - { - mssError(1,"HTRB","Netscape 4.x or W3C DOM support required"); - return -1; - } - - /** Get an id for this. **/ - id = (HTRB.idcnt++); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { - mssError(1,"HTRB","RadioButtonPanel widget must have a 'width' property"); - return -1; - } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { - mssError(1,"HTRB","RadioButtonPanel widget must have a 'height' property"); - return -1; - } - - /** Background color/image? **/ - htrGetBackground(tree,NULL,!s->Capabilities.Dom0NS,main_background,sizeof(main_background)); - - /** Text color? **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(textcolor,ptr,sizeof(textcolor)); - else - strcpy(textcolor,"black"); - - /** Outline color? **/ - htrGetBackground(tree,"outline",!s->Capabilities.Dom0NS,outline_background,sizeof(outline_background)); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Get title **/ - if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(title,ptr,sizeof(title)); - - /** User requesting expression for selected tab? **/ - htrCheckAddExpression(s, tree, name, "value"); - - /** User requesting expression for selected tab using integer index value? **/ - htrCheckAddExpression(s, tree, name, "value_index"); - - /** Get fieldname **/ - if (wgtrGetPropertyValue(tree,"fieldname",DATA_T_STRING,POD(&ptr)) == 0) - { - strtcpy(fieldname,ptr,sizeof(fieldname)); - } - else - { - fieldname[0]='\0'; - } - - if (wgtrGetPropertyValue(tree,"form",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(form,ptr,sizeof(form)); - else - form[0]='\0'; - - htrAddScriptInclude(s, "/sys/js/htdrv_radiobutton.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - +int htrbRender(pHtSession s, pWgtrNode tree, int z) + { + char* ptr; + + /** Verify required capabilities. **/ + if (!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) + { + mssError(1, "HTRB", "Netscape 4.x or W3C DOM support required"); + return -1; + } + + /** Get an id for this widget. **/ + const int id = (HTRB.idcnt++); + + /** Get x,y,w,h of this object. **/ + int x, y, w, h, spacing; + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) x = 0; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) y = 0; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) + { + mssError(1,"HTRB","RadioButtonPanel widget must have a 'width' property"); + return -1; + } + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) + { + mssError(1,"HTRB","RadioButtonPanel widget must have a 'height' property"); + return -1; + } + if (wgtrGetPropertyValue(tree, "spacing", DATA_T_INTEGER, POD(&spacing)) != 0) spacing = 10; + + /** Get the name and title attributes. **/ + char name[64] = "", title[64] = ""; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTRB", "RadioButtonPanel widget must have a 'name' property"); + return -1; + } + strtcpy(name, ptr, sizeof(name)); + if (wgtrGetPropertyValue(tree, "title", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTRB", "RadioButtonPanel widget must have a 'title' property"); + return -1; + } + strtcpy(title, ptr, sizeof(title)); + + /** Get text color attribute. **/ + char textcolor[32]; + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(textcolor, ptr, sizeof(textcolor)); + else + strcpy(textcolor, "black"); + + /** Get background attributes. **/ + char main_background[128] = ""; + char outline_background[128] = ""; + htrGetBackground(tree, NULL, !s->Capabilities.Dom0NS, main_background, sizeof(main_background)); + htrGetBackground(tree, "outline", !s->Capabilities.Dom0NS, outline_background, sizeof(outline_background)); + + /** User requesting expression for selected tab? **/ + htrCheckAddExpression(s, tree, name, "value"); + + /** User requesting expression for selected tab using integer index value? **/ + htrCheckAddExpression(s, tree, name, "value_index"); + + /** Get fieldname and form attributes. **/ + char fieldname[32] = "", form[64] = ""; + if (wgtrGetPropertyValue(tree, "fieldname", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(fieldname, ptr, sizeof(fieldname)); + if (wgtrGetPropertyValue(tree, "form", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(form, ptr, sizeof(form)); + + + /** Include scripts. **/ + htrAddScriptInclude(s, "/sys/js/htdrv_radiobutton.js", 0); + htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); + + /** Link DOM node to widget data. **/ + htrAddWgtrObjLinkage_va(s, tree, "rb%POSparent", id); + htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(htr_subel(_obj, 'rb%POSborder'), 'rb%POScover')", id, id); + + /** Script initialization call. **/ + if (strlen(main_background) > 0) + { + htrAddScriptInit_va(s, "{ " + "const parentPane = wgtrGetNodeRef(ns, '%STR&SYM'); " + "const borderPane = htr_subel(parentPane, 'rb%POSborder'); " + "const coverPane = htr_subel(borderPane, 'rb%POScover'); " + "const titlePane = htr_subel(parentPane, 'rb%POStitle'); " + "radiobuttonpanel_init({ " + "parentPane, borderPane, coverPane, titlePane, " + "fieldname:'%STR&JSSTR', " + "mainBackground:'%STR&JSSTR', " + "outlineBackground:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "}); }\n", + name, id, id, id, + fieldname, + main_background, + outline_background, + form + ); + } + else + { + htrAddScriptInit_va(s, + "radiobuttonpanel_init({ " + "parentPane:wgtrGetNodeRef(ns, '%STR&SYM'), " + "fieldname:'%STR&JSSTR', " + "borderPane:0, " + "coverPane:0, " + "titlePane:0, " + "mainBackground:0, " + "outlineBackground:0, " + "form:'%STR&JSSTR', " + "});\n", + name, + fieldname, + form + ); + } + + /** Add event listenners. **/ + htrAddEventHandlerFunction(s, "document", "MOUSEUP", "radiobutton", "radiobutton_mouseup"); + htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "radiobutton", "radiobutton_mousedown"); + htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "radiobutton", "radiobutton_mouseover"); + htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "radiobutton", "radiobutton_mousemove"); + + /** Write style headers for container DOM nodes. **/ const int para_height = s->ClientInfo->ParagraphHeight; - fprintf(stderr, "h: %d\n", para_height); const int top_offset = (para_height * 3) / 4 + 1; htrAddStylesheetItem_va(s, "#rb%POSparent { " @@ -201,216 +246,173 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) { para_height, z + 3 ); - - htrAddScriptGlobal(s, "radiobutton", "null", 0); - - /** DOM linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "rb%POSparent",id); - htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(htr_subel(_obj,\"rb%POSborder\"),\"rb%POScover\")",id,id); - - /** Loop through each radiobutton and flag it NOOBJECT **/ - raido_button_count = 0; - for (j=0;jChildren));j++) - { - radiobutton_obj = xaGetItem(&(tree->Children), j); - radiobutton_obj->RenderFlags |= HT_WGTF_NOOBJECT; - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (strcmp(ptr,"widget/radiobutton") == 0) raido_button_count++; - } - /** Compute values for laying out radio buttons. **/ - const int cover_height = h - top_offset - 6; - int item_spacing = para_height + 12; - int cover_margin = 10; - if (item_spacing * raido_button_count + 2*cover_margin > cover_height) - item_spacing = (cover_height-2*cover_margin)/raido_button_count; - if (item_spacing * raido_button_count + 2*cover_margin > cover_height) - cover_margin = (cover_height-(item_spacing*raido_button_count))/2; - if (cover_margin < 2) cover_margin = 2; - i = 1; - for (j=0;jChildren));j++) - { - radiobutton_obj = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) - { - htrAddStylesheetItem_va(s, - "#rb%POSoption%POS { " - "position:absolute; " - "visibility:inherit; " - "overflow:hidden; " - "left:7px; " - "top:%INTpx; " - "width:calc(100%% - 7px); " - "height:%POSpx; " - "z-index:%POS; " - "}\n", - id, i, - cover_margin + ((i-1) * item_spacing) + 2, - min(item_spacing, para_height + 4), - z + 2 - ); - i++; - } - } - - /** Script initialization call. **/ - if (strlen(main_background) > 0) { - htrAddScriptInit_va(s, - " var rb = wgtrGetNodeRef(ns, \"%STR&SYM\");\n" - " radiobuttonpanel_init({\n" - " parentPane:rb, fieldname:\"%STR&JSSTR\",\n" - " borderPane:htr_subel(rb,\"rb%POSborder\"),\n" - " coverPane:htr_subel(htr_subel(rb,\"rb%POSborder\"),\"rb%POScover\"),\n" - " titlePane:htr_subel(rb,\"rb%POStitle\"),\n" - " mainBackground:\"%STR&JSSTR\", outlineBackground:\"%STR&JSSTR\", form:\"%STR&JSSTR\"});\n", - name, fieldname, id, id,id, id, main_background, outline_background, form); - } else { - htrAddScriptInit_va(s," radiobuttonpanel_init({parentPane:wgtrGetNodeRef(ns,\"%STR&SYM\"), fieldname:\"%STR&JSSTR\", borderPane:0, coverPane:0, titlePane:0, mainBackground:0, outlineBackground:0, form:\"%STR&JSSTR\"});\n", name, fieldname, form); - } - - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "radiobutton", "radiobutton_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "radiobutton", "radiobutton_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "radiobutton", "radiobutton_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "radiobutton", "radiobutton_mousemove"); - - /* - Now lets loop through and add each radiobutton - */ - i = 1; - for (j=0;jChildren));j++) + + /** Write HTML to contain the radio buttons. **/ + htrAddBodyItem_va(s, "
\n", id); + htrAddBodyItem_va(s, "
\n", id); + htrAddBodyItem_va(s, "
\n", id); + + /** Search child array for radio buttons. **/ + XArray radio_buttons; + xaInit(&radio_buttons, tree->Children.nItems); + for (int i = 0; i < tree->Children.nItems; i++) { - sub_tree = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(sub_tree,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) - { - if (wgtrGetPropertyValue(sub_tree,"value",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(value, ptr, sizeof(value)); - else - value[0] = '\0'; - if (wgtrGetPropertyValue(sub_tree,"label",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(label, ptr, sizeof(label)); - else - label[0] = '\0'; - is_selected = htrGetBoolean(sub_tree, "selected", 0); - if (is_selected < 0) is_selected = 0; - htrAddWgtrObjLinkage_va(s,sub_tree,"rb%POSoption%POS",id,i); - wgtrGetPropertyValue(sub_tree,"name",DATA_T_STRING,POD(&ptr)); - htrAddScriptInit_va(s, - " var rbitem = wgtrGetNodeRef('%STR&SYM', '%STR&SYM');\n" - " add_radiobutton(rbitem, {selected:%INT, buttonset:htr_subel(rbitem, \"rb%POSbuttonset%POS\"), buttonunset:htr_subel(rbitem, \"rb%POSbuttonunset%POS\"), value:htr_subel(rbitem, \"rb%POSvalue%POS\"), label:htr_subel(rbitem, \"rb%POSlabel%POS\"), valuestr:\"%STR&JSSTR\", labelstr:\"%STR&JSSTR\"});\n", - wgtrGetNamespace(sub_tree), ptr, - is_selected, - id, i, id, i, - id, i, id, i, - value, label); - i++; - } - else - { - htrRenderWidget(s, sub_tree, z+1); - } + pWgtrNode child = tree->Children.Items[i]; + + /** Mark child as no-object. **/ + child->RenderFlags |= HT_WGTF_NOOBJECT; + + /** Add radio buttons to the array, render other widgets immediately (so we can forget about them). **/ + wgtrGetPropertyValue(child, "outer_type", DATA_T_STRING, POD(&ptr)); + if (strcmp(ptr, "widget/radiobutton") == 0) xaAddItem(&radio_buttons, child); + else htrRenderWidget(s, child, z + 1); } - - /* - Do the HTML layers - */ - htrAddBodyItem_va(s,"
\n", id); - htrAddBodyItem_va(s,"
\n", id); - htrAddBodyItem_va(s,"
\n", id); - - /* Loop through each radio button and do the option pane and sub layers */ - i = 1; - for (j=0;jChildren));j++) + + /** Write style for radio buttons. **/ + const int top_padding = 12; + const int button_height = para_height + 2; + const int content_height = top_padding + (button_height * radio_buttons.nItems) + 8; + for (int i = 0; i < radio_buttons.nItems; i++) { - radiobutton_obj = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) - { - /** CSS layers **/ - htrAddStylesheetItem_va(s, - "#rb%POSbuttonset%POS, " - "#rb%POSbuttonunset%POS { " - "position:absolute; " - "overflow:hidden; " - "left:5px; " - "top:%INTpx; " - "width:12px; " - "height:12px; " - "z-index:%POS; " - "cursor:pointer; " - "}\n", - id, i, - id, i, - (para_height / 2) - 3, - z + 2 - ); - htrAddStylesheetItem_va(s, - "#rb%POSvalue%POS { " - "position:absolute; " - "visibility:hidden; " - "overflow:hidden; " - "left:5px; " - "top:6px; " - "width:12px; " - "height:12px; " - "z-index:%POS; " - "}\n", - id, i, - z + 2 - ); - htrAddStylesheetItem_va(s, - "#rb%POSlabel%POS { " - "position:absolute; " - "visibility:inherit; " - "overflow:hidden; " - "left:27px; " - "top:3px; " - "width:calc(100%% - 27px); " - "height:%POSpx; " - "z-index:%POS; " - "cursor:pointer; " - "}\n", - id, i, - item_spacing - 1, - z + 2 - ); - - /** Body layers **/ - htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); - - wgtrGetPropertyValue(radiobutton_obj,"label",DATA_T_STRING,POD(&ptr)); - strtcpy(sbuf2,ptr,sizeof(sbuf2)); - htrAddBodyItem_va(s,"
%STR&HTE
\n", - id, i, textcolor, sbuf2); - - /* use label (from above) as default value if no value given */ - if(wgtrGetPropertyValue(radiobutton_obj,"value",DATA_T_STRING,POD(&ptr))==0) - { - strtcpy(sbuf2,ptr,sizeof(sbuf2)); - } - - htrAddBodyItem_va(s," \n", - id, i, sbuf2); - htrAddBodyItem(s, "
\n"); - i++; - } + pWgtrNode radio_button = radio_buttons.Items[i]; + + /** Store data for the individual radio button. **/ + char value_buf[64] = "", label_buf[64] = ""; + if (wgtrGetPropertyValue(radio_button, "value", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(value_buf, ptr, sizeof(value_buf)); + if (wgtrGetPropertyValue(radio_button, "label", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(label_buf, ptr, sizeof(label_buf)); + const int is_selected = (htrGetBoolean(radio_button, "selected", 0) > 0); + wgtrGetPropertyValue(radio_button, "name", DATA_T_STRING, POD(&ptr)); + + /** Create pointers to data. **/ + char* name = ptr; /* Name temporarily stored in the wgtrGetPropertyValue() buffer. */ + char* value = value_buf; + char* label = (label_buf[0] == '\0') ? value_buf : label_buf; + + /** Link the radio button DOM node to widget data. **/ + htrAddWgtrObjLinkage_va(s, radio_button, "rb%POSoption%POS", id, i); + + /** Write the initialization call. **/ + htrAddScriptInit_va(s, "{ " + "const rbitem = wgtrGetNodeRef('%STR&SYM', '%STR&SYM');" + "add_radiobutton(rbitem, { " + "selected:%POS, " + "buttonset:htr_subel(rbitem, 'rb%POSbuttonset%POS'), " + "buttonunset:htr_subel(rbitem, 'rb%POSbuttonunset%POS'), " + "value:htr_subel(rbitem, 'rb%POSvalue%POS'), " + "label:htr_subel(rbitem, 'rb%POSlabel%POS'), " + "valuestr:'%STR&JSSTR', " + "labelstr:'%STR&JSSTR', " + "}); }\n", + wgtrGetNamespace(radio_button), name, + is_selected, + id, i, id, i, + id, i, id, i, + value, label + ); + + /** Write CSS for the radio button container. **/ + const int base_top = top_padding + (button_height * i); + const double percent_space_above = (100.0 / radio_buttons.nItems) * i; + const double content_above = ((double)content_height / radio_buttons.nItems) * i; + htrAddStylesheetItem_va(s, + "#rb%POSoption%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:7px; " + "top:calc(0px " + "+ %POSpx " + "+ min(%DBL%% - %DBLpx, %POSpx) " + "); " + "width:calc(100%% - 14px); " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, i, + base_top, + percent_space_above, content_above, spacing * i, + button_height, + z + 2 + ); + + /** Write CSS for the radio button elements. **/ + htrAddStylesheetItem_va(s, + "#rb%POSbuttonset%POS, " + "#rb%POSbuttonunset%POS { " + "position:absolute; " + "overflow:hidden; " + "left:5px; " + "top:%INTpx; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + id, i, + (para_height / 2) - 3, + z + 2 + ); + htrAddStylesheetItem_va(s, + "#rb%POSvalue%POS { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:5px; " + "top:6px; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "}\n", + id, i, + z + 2 + ); + htrAddStylesheetItem_va(s, + "#rb%POSlabel%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:27px; " + "top:3px; " + "width:calc(100%% - 27px); " + "height:calc(100%% - 1px); " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + z + 2 + ); + + /** Write radio button HTML. **/ + htrAddBodyItem_va(s, "
\n", id, i); + htrAddBodyItem_va(s, " \n", id, i); + htrAddBodyItem_va(s, "
\n", id, i); + htrAddBodyItem_va(s, "
%STR&HTE
\n", id, i, textcolor, label); + htrAddBodyItem_va(s, " \n", id, i, value); + htrAddBodyItem (s, "
\n"); } - - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem_va(s,"
%STR&HTE
\n", id, textcolor, title); - htrAddBodyItem(s, "
\n"); - - return 0; -} + + htrAddBodyItem_va(s, + "
\n" + "
\n" + "
%STR&HTE
\n" + "
\n", + id, textcolor, title + ); + + return 0; + } /** htrbInitialize - register with the ht_render module. **/ int htrbInitialize() { pHtDriver drv; + + /** Initialize globals. */ + HTRB.idcnt = 0; /** Allocate the driver **/ drv = htrAllocDriver(); @@ -430,12 +432,9 @@ int htrbInitialize() { htrAddEvent(drv,"MouseMove"); htrAddEvent(drv,"DataChange"); - /** Register. **/ + /** Register with dhtml support. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); - HTRB.idcnt = 0; - return 0; } From 16a8e3e9bf3d0d304e81e0d46de8d2649d6a391e Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 11 Feb 2026 12:13:47 -0700 Subject: [PATCH 056/101] Clean up some JS for the radio button panel widget. --- centrallix-os/sys/js/htdrv_radiobutton.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_radiobutton.js b/centrallix-os/sys/js/htdrv_radiobutton.js index 2d5c9eb77..2902d7525 100644 --- a/centrallix-os/sys/js/htdrv_radiobutton.js +++ b/centrallix-os/sys/js/htdrv_radiobutton.js @@ -37,7 +37,6 @@ function rb_setvalue(v) { } } this.clearvalue(); - //alert('Warning: "'+v+'" is not in the radio button list.'); } function rb_cb_getvalue(p) { @@ -104,12 +103,8 @@ function add_radiobutton(optionPane, param) { var rb = wgtrGetParent(optionPane); rb.rbCount++; optionPane.valueIndex = rb.rbCount; - /*optionPane.kind = 'radiobutton'; - optionPane.document.layer = optionPane;*/ htr_init_layer(optionPane, rb, 'radiobutton'); - //optionPane.mainlayer = rb; optionPane.optionPane = optionPane; - optionPane.isSelected = param.selected; optionPane.valueStr = param.valuestr; optionPane.labelStr = param.labelstr; @@ -349,8 +344,6 @@ function rb_changemode(){ for (var i=0;i Date: Wed, 11 Feb 2026 12:37:42 -0700 Subject: [PATCH 057/101] Add Easter Eggs for code reviewers to find. --- centrallix-doc/Widgets/widgets.xml | 2 +- centrallix-os/sys/js/ht_geom_dom1html.js | 1 + centrallix/htmlgen/htdrv_dropdown.c | 1 + centrallix/htmlgen/htdrv_radiobutton.c | 1 + centrallix/htmlgen/htdrv_scrollpane.c | 2 +- centrallix/htmlgen/htdrv_tab.c | 3 ++- centrallix/wgtr/apos.c | 2 ++ 7 files changed, 9 insertions(+), 3 deletions(-) diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index 1dea58f85..e75c6df90 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -2824,7 +2824,7 @@ osrc1 "widget/osrc" Starts a new app in a new window. - Logs data to the console (using console.log()), for testing and debugging. Set the 'Message' to specify a text string that should appear in the log. + Logs data to the console (using console.log()), for testing and debugging. Set the 'Message' to specify a text string that should appear in the log. Might be useful for logging Easter Egg #8. Loads the page. diff --git a/centrallix-os/sys/js/ht_geom_dom1html.js b/centrallix-os/sys/js/ht_geom_dom1html.js index 71d29db99..f2687cc3c 100644 --- a/centrallix-os/sys/js/ht_geom_dom1html.js +++ b/centrallix-os/sys/js/ht_geom_dom1html.js @@ -405,6 +405,7 @@ function setResponsive(l, value, d) { var fl_parent = l['__fl_parent_' + d2] ?? wgtrGetServerProperty(l, 'fl_parent_' + d2); if (fl_parent == undefined || fl_parent == null) { + /** I wonder if anyone reviewers will see this: Easter egg #7. **/ const warningMsg = 'setResponsive() - FAIL: Missing ' + ((wgtrIsNode(l)) ? 'wgtr.' : '__') + 'fl_parent_' + d2; console.warn(warningMsg, l); } diff --git a/centrallix/htmlgen/htdrv_dropdown.c b/centrallix/htmlgen/htdrv_dropdown.c index f2af95d00..8f3d586ec 100644 --- a/centrallix/htmlgen/htdrv_dropdown.c +++ b/centrallix/htmlgen/htdrv_dropdown.c @@ -52,6 +52,7 @@ static struct { #define HTDD_DYNAMIC 2 #define HTDD_DYNAMIC_CLIENT HTDD_DYNAMIC #define HTDD_OBJECTSOURCE 3 +#define HTDD_EASTER_EGG_4 4 /* htddRender - generate the HTML code for the page. diff --git a/centrallix/htmlgen/htdrv_radiobutton.c b/centrallix/htmlgen/htdrv_radiobutton.c index 5946e048c..27024d363 100644 --- a/centrallix/htmlgen/htdrv_radiobutton.c +++ b/centrallix/htmlgen/htdrv_radiobutton.c @@ -140,6 +140,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) "const borderPane = htr_subel(parentPane, 'rb%POSborder'); " "const coverPane = htr_subel(borderPane, 'rb%POScover'); " "const titlePane = htr_subel(parentPane, 'rb%POStitle'); " + "const easterEgg2 = 'Easter Egg #2';" "radiobuttonpanel_init({ " "parentPane, borderPane, coverPane, titlePane, " "fieldname:'%STR&JSSTR', " diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index 2a4b36154..7b012e95e 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -346,7 +346,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) "" /** Close the scrollpane table (see above). **/ - "\n", + " \n", h - 36 ); diff --git a/centrallix/htmlgen/htdrv_tab.c b/centrallix/htmlgen/htdrv_tab.c index 715a5a6e2..75eaefdee 100644 --- a/centrallix/htmlgen/htdrv_tab.c +++ b/centrallix/htmlgen/htdrv_tab.c @@ -491,7 +491,8 @@ httabRender(pHtSession s, pWgtrNode tree, int z) "box-shadow:%DBLpx %DBLpx %POSpx %STR&CSSVAL; " "text-align:%STR&CSSVAL; " "color:%STR&CSSVAL; " - "font-weight:bold; " + "font-weight:bold; /*" + "easter-egg-6:value;*/ " "background-position: %INTpx %INTpx; " "%STR " "}\n", diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 06550eda0..ea42c2e63 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -81,6 +81,8 @@ *** XArray: This array also stores its size (nAlloc) and the number of items *** stored (nItems), so you don't have to pass that info separately. *** + *** Easter Egg #1: I wonder if any reviewers will find this... + *** *** SWidgets, CWidgets, and EWidgets: Lines record which widgets start, cross, *** and end on them. These categories are exclusive, so a widget which *** starts on a given line will be in the SWidgets list but it will not be From 4cb04a59427c7ae5f682c55d051bb3e671685466 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 11 Feb 2026 12:48:40 -0700 Subject: [PATCH 058/101] Fix a bug in htdrv_radiobutton.js. --- centrallix-os/sys/js/htdrv_radiobutton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/centrallix-os/sys/js/htdrv_radiobutton.js b/centrallix-os/sys/js/htdrv_radiobutton.js index 2902d7525..9f389ba4a 100644 --- a/centrallix-os/sys/js/htdrv_radiobutton.js +++ b/centrallix-os/sys/js/htdrv_radiobutton.js @@ -384,7 +384,7 @@ function rb_changemode(){ } function radiobuttonpanel_init(param) { - const { parentPane, borderpane, coverpane, titlepane } = param; + const { parentPane, borderPane: borderpane, coverPane: coverpane, titlePane: titlepane } = param; if (cx__capabilities.Dom1HTML) titlepane.styleobj = titlepane.getElementsByTagName('table')[0]; From e468f073291a30f8b123d43899c7367769c66c55 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 11 Feb 2026 12:49:04 -0700 Subject: [PATCH 059/101] Tweak size of selection area in htdrv_radiobutton.js. --- centrallix-os/sys/js/htdrv_radiobutton.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/centrallix-os/sys/js/htdrv_radiobutton.js b/centrallix-os/sys/js/htdrv_radiobutton.js index 9f389ba4a..5aca57c55 100644 --- a/centrallix-os/sys/js/htdrv_radiobutton.js +++ b/centrallix-os/sys/js/htdrv_radiobutton.js @@ -147,9 +147,9 @@ function add_radiobutton(optionPane, param) { } optionPane.yOffset = getRelativeY(optionPane)+getRelativeY(rb.coverPane)+getRelativeY(rb.borderPane); - optionPane.area = pg_addarea(rb, getRelativeX(optionPane), optionPane.yOffset, getClipWidth(optionPane), pg_parah+4, optionPane, 'rb', 3); + optionPane.area = pg_addarea(rb, getRelativeX(optionPane), optionPane.yOffset - 1, getClipWidth(optionPane), pg_parah+4, optionPane, 'rb', 3); optionPane.area.__defineGetter__('width', () => parseInt(getComputedStyle(optionPane).width)); - optionPane.area.__defineGetter__('height', () => parseInt(getComputedStyle(optionPane).height)); + optionPane.area.__defineGetter__('height', () => parseInt(getComputedStyle(optionPane).height) + 3); } function rb_getfocus(xo,yo,l,c,n,a,from_kbd) From 527a3b90ba08e7776bde230db526ca7fccdd4383 Mon Sep 17 00:00:00 2001 From: Lightning11wins Date: Wed, 11 Feb 2026 14:53:24 -0700 Subject: [PATCH 060/101] Fix some inconsistent style issues with generated HTML & CSS. Fix inconsistent CSS rules in the style tag for the document head. Fix inconsistent indentation of head tags. Fix code style mistakes in generation code. Update examples in docs to use correct styling. Update outdated generation techniques. Clean up unused code in the generation process. --- centrallix-sysdoc/HTDriver_Authoring.md | 6 +- centrallix/htmlgen/ht_render.c | 15 +- centrallix/htmlgen/htdrv_autolayout.c | 20 +-- centrallix/htmlgen/htdrv_button.c | 208 ++++++++++++++++++++---- centrallix/htmlgen/htdrv_calendar.c | 13 +- centrallix/htmlgen/htdrv_chart.c | 2 +- centrallix/htmlgen/htdrv_checkbox.c | 2 +- centrallix/htmlgen/htdrv_clock.c | 6 +- centrallix/htmlgen/htdrv_component.c | 19 ++- centrallix/htmlgen/htdrv_datetime.c | 8 +- centrallix/htmlgen/htdrv_dropdown.c | 39 +++-- centrallix/htmlgen/htdrv_editbox.c | 38 ++++- centrallix/htmlgen/htdrv_fileupload.c | 8 +- centrallix/htmlgen/htdrv_formstatus.c | 2 +- centrallix/htmlgen/htdrv_frameset.c | 2 +- centrallix/htmlgen/htdrv_html.c | 44 +++-- centrallix/htmlgen/htdrv_image.c | 19 ++- centrallix/htmlgen/htdrv_imagebutton.c | 2 +- centrallix/htmlgen/htdrv_label.c | 28 +++- centrallix/htmlgen/htdrv_map.c | 15 +- centrallix/htmlgen/htdrv_menu.c | 60 ++++++- centrallix/htmlgen/htdrv_multiscroll.c | 45 ++++- centrallix/htmlgen/htdrv_objcanvas.c | 17 +- centrallix/htmlgen/htdrv_osrc.c | 14 +- centrallix/htmlgen/htdrv_page.c | 74 +++++---- centrallix/htmlgen/htdrv_pane.c | 13 +- centrallix/htmlgen/htdrv_radiobutton.c | 16 +- centrallix/htmlgen/htdrv_scrollbar.c | 43 ++++- centrallix/htmlgen/htdrv_scrollpane.c | 10 +- centrallix/htmlgen/htdrv_spinner.c | 12 +- centrallix/htmlgen/htdrv_tab.c | 13 +- centrallix/htmlgen/htdrv_table.c | 8 +- centrallix/htmlgen/htdrv_terminal.c | 22 ++- centrallix/htmlgen/htdrv_textarea.c | 9 - centrallix/htmlgen/htdrv_textbutton.c | 24 +-- centrallix/htmlgen/htdrv_treeview.c | 20 ++- centrallix/htmlgen/htdrv_window.c | 25 ++- centrallix/utility/iface_html.c | 3 +- 38 files changed, 676 insertions(+), 248 deletions(-) diff --git a/centrallix-sysdoc/HTDriver_Authoring.md b/centrallix-sysdoc/HTDriver_Authoring.md index 430bab645..a4a221132 100644 --- a/centrallix-sysdoc/HTDriver_Authoring.md +++ b/centrallix-sysdoc/HTDriver_Authoring.md @@ -194,11 +194,11 @@ The 'parentobj' parameter is the name of the "layer" object that will contain th The following list of functions are used by widget drivers to actually generate the DHTML and scripting needed to support widget instances. #### htrAddStylesheetItem(pHtSession s, char* html_text) returns int -This functions adds a stylesheet definition to the header between the tags ``. It is recommended to maintain a consistant indention style (using a tab '\t' character) at the beginning of these lines. +This functions adds a stylesheet definition to the header between the tags ``. It is recommended to maintain a consistant indention style (using a tab '\t' character) at the beginning of these lines. Example: -`htrAddStylesheetItem(s, "\t#pgtop { POSITION:absolute; VISIBILITY:hidden;");` +`htrAddStylesheetItem(s, "\t\t#pgtop { position:absolute; visibility:hidden; }");` #### htrAddHeaderItem(pHtSession s, char* html_text) returns int This function adds text directly to the HTML header section of the generated document (between `` and ``). The html_text string's content is copied into subsystem data structures. @@ -209,7 +209,7 @@ The 'pHtSession' parameter 's' is passed to the Render() function as the first p Example: -`htrAddHeaderItem(s, " This is a title\n");` +`htrAddHeaderItem(s, "\tThis is a title\n");` #### htrAddBodyItem(pHtSession s, char* html_text) returns int This function adds text directly into the HTML body of the document, and works in a manner identical to the above HeaderItem function. diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 871eb258d..915abfdea 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1811,7 +1811,6 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe htrAddScriptInit_va(s, "\n var ns = \"%STR&SYM\";\n", /*" var rootname = \"%STR&SYM\";\n", */ s->Namespace->DName /*, s->Namespace->DName */); - /*htrAddStylesheetItem(s, "\tdiv {position:absolute; visibility:inherit; overflow:hidden; }\n");*/ /** Render the top-level widget -- the function that's run * underneath will be dependent upon what the widget @@ -1831,7 +1830,7 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe #ifdef WGTR_DBG_WINDOW htrAddScriptWgtr_va(s, " wgtrWalk(%STR&SYM);\n", tree->Name); htrAddScriptWgtr(s, " ifcLoadDef(\"net/centrallix/button.ifc\");\n"); - htrAddStylesheetItem(s, "\t#dbgwnd {position: absolute; top: 400; left: 50;}\n"); + htrAddStylesheetItem(s, "\t\t#dbgwnd {position: absolute; top: 400; left: 50;}\n"); htrAddBodyItem(s, "
" "" "
\n"); @@ -1868,12 +1867,12 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe htrQPrintf(s, "\n" "\n" - " \n" - " \n" - " \n" + "\t\n" + "\t\n" + "\t\n" , cx__version); - htrWrite(s, " \n", -1); + htrWrite(s, "\t\n", -1); /** Write the HTML header items. **/ for(i=0;iPage.HtmlHeader.nItems;i++) { @@ -2700,7 +2699,7 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y /** Generate the style CSS **/ htrAddStylesheetItem_va(s, - "\t%STR {" + "\t\t%STR { " "left:"ht_flex_format"; " "top:"ht_flex_format"; " "%[width:"ht_flex_format"; %]" // BUG! diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index 02d6d7d2a..f1e62b3d5 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -100,16 +100,16 @@ htalRender(pHtSession s, pWgtrNode tree, int z) /** Add the stylesheet for the layer **/ htrAddStylesheetItem_va(s, - "\t#al%POSbase { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "OVERFLOW:visible; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "HEIGHT:"ht_flex_format"; " - "Z-INDEX:%POS; " - "}\n", + "\t\t#al%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:visible; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n ", id, ht_flex_x(x, tree), ht_flex_y(y, tree), diff --git a/centrallix/htmlgen/htdrv_button.c b/centrallix/htmlgen/htdrv_button.c index 3e3f7c577..0af5a8dfd 100644 --- a/centrallix/htmlgen/htdrv_button.c +++ b/centrallix/htmlgen/htdrv_button.c @@ -151,7 +151,7 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if (!strcmp(type,"image") || !strcmp(type,"textoverimage")) { htrAddStylesheetItem_va(s, - "\t#gb%POSpane { " + "\t\t#gb%POSpane { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " @@ -169,7 +169,9 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) /** Button click animation. **/ if (is_enabled) { htrAddStylesheetItem_va(s, - "\t#tb%POSpane:active { transform: translate(1px, 1px); }\n", + "\t\t#tb%POSpane:active { " + "transform:translate(1px, 1px); " + "}\n", id ); } @@ -215,19 +217,17 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if(!strcmp(type,"textoverimage")) { htrAddStylesheetItem_va(s, - "\t#gb%POSpane2 { " + "\t\t#gb%POSpane2 { " "position:absolute; " "visibility:inherit; " - "left:%POS; " - "top:%POS; " + "left:0px; " + "top:0px; " "width:"ht_flex_format"; " "z-index:%POS; " "}\n", id, - 0, - 0, - ht_flex(w, tree->Parent->width, ht_get_fl_w(tree)), - z+1 + ht_flex_w(w, tree), + z + 1 ); htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h,fgcolor1,text); } @@ -262,13 +262,107 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if(s->Capabilities.Dom0NS) { /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:"ht_flex_format"; TOP:"ht_flex_format"; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,ht_flex(x,tree->Parent->width,ht_get_fl_x(tree)),ht_flex(y,tree->Parent->height,ht_get_fl_y(tree)),ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:%STR; LEFT:-1; TOP:-1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_enabled?"inherit":"hidden",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+1); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_enabled?"hidden":"inherit",ht_flex(w-1,tree->Parent->width,ht_get_fl_w(tree)),z+1); - htrAddStylesheetItem_va(s,"\t#gb%POStop { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSbtm { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:"ht_flex_format"; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",ht_flex(w,tree->Parent->width,ht_get_fl_w(tree)),z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSrgt { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSlft { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane2 { " + "position:absolute; " + "visibility:%STR; " + "left:-1px; " + "top:-1px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + (is_enabled) ? "inherit" : "hidden", + ht_flex_w(w, tree), + z + 1 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane3 { " + "position:absolute; " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + (is_enabled) ? "hidden" : "inherit", + ht_flex_w(w - 1, tree), + z + 1 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POStop { " + "position:absolute; " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "height:1px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + (is_ts) ? "hidden" : "inherit", + ht_flex_w(w, tree), + z + 2 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSbtm { " + "position:absolute; " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "height:1px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + (is_ts) ? "hidden" : "inherit", + ht_flex_w(w, tree), + z + 2 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSrgt { " + "position:absolute; " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "height:1px; " + "width:1px; " + "z-index:%POS; " + "}\n", + id, + (is_ts) ? "hidden" : "inherit", + z + 2 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSlft { " + "position:absolute; " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "height:1px; " + "width:1px; " + "z-index:%POS; " + "}\n", + id, + (is_ts) ? "hidden" : "inherit", + z + 2 + ); /** Script initialization call. **/ htrAddScriptInit_va(s, " gb_init({layer:%STR&SYM, layer2:htr_subel(%STR&SYM, \"gb%POSpane2\"), layer3:htr_subel(%STR&SYM, \"gb%POSpane3\"), top:htr_subel(%STR&SYM, \"gb%POStop\"), bottom:htr_subel(%STR&SYM, \"gb%POSbtm\"), right:htr_subel(%STR&SYM, \"gb%POSrgt\"), left:htr_subel(%STR&SYM, \"gb%POSlft\"), width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR', n:\"%STR&JSSTR\", p:\"%STR&JSSTR\", c:\"%STR&JSSTR\", d:\"%STR&JSSTR\", type:\"%STR&JSSTR\"});\n", @@ -344,7 +438,7 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if(h >=0 ) { htrAddStylesheetItem_va(s, - "\t#gb%POSpane { " + "\t\t#gb%POSpane { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -354,18 +448,30 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) "z-index:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w - 1 - 2 * box_offset, tree), z ); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2, #gb%POSpane3 { height: "ht_flex_format";}\n",id,id,ht_flex(h-3,tree->Parent->height,ht_get_fl_h(tree))); - htrAddStylesheetItem_va(s,"\t#gb%POSpane { height: "ht_flex_format";}\n",id,ht_flex(h-1-2*box_offset,tree->Parent->height,ht_get_fl_h(tree))); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane2, #gb%POSpane3 { " + "height: "ht_flex_format"; " + "}\n", + id, id, + ht_flex_h(h - 3, tree) + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane { " + "height:"ht_flex_format"; " + "}\n", + id, + ht_flex_h(h - 1 - 2 * box_offset, tree) + ); } else { htrAddStylesheetItem_va(s, - "\t#gb%POSpane { " + "\t\t#gb%POSpane { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -375,17 +481,57 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) "z-index:%POS; " "}\n", id, - ht_flex(x, tree->Parent->width, ht_get_fl_x(tree)), - ht_flex(y, tree->Parent->height, ht_get_fl_y(tree)), - ht_flex(w-1-2*box_offset, tree->Parent->width, ht_get_fl_w(tree)), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w - 1 - 2 * box_offset, tree), z ); } - htrAddStylesheetItem_va(s,"\t#gb%POSpane, #gb%POSpane2, #gb%POSpane3 { cursor:default; text-align: center; }\n",id,id,id); - htrAddStylesheetItem_va(s,"\t#gb%POSpane { %STR border-width: 1px; border-style: solid; border-color: white gray gray white; }\n",id,bgstyle); - /*htrAddStylesheetItem_va(s,"\t#gb%dpane { color: %s; }\n",id,fgcolor2);*/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:-1px; top: -1px; width:"ht_flex_format"; }\n",id,is_enabled?"inherit":"hidden",z+1,ht_flex(w-3,tree->Parent->width,ht_get_fl_w(tree))); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:0px; top: 0px; width:"ht_flex_format"; }\n",id,is_enabled?"hidden":"inherit",z+1,ht_flex(w-3,tree->Parent->width,ht_get_fl_w(tree))); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane, #gb%POSpane2, #gb%POSpane3 { " + "cursor:default; " + "text-align:center; " + "}\n", + id, id, id + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane { " + "%STR " + "border-width:1px; " + "border-style:solid; " + "border-color:white gray gray white; " + "}\n", + id, + bgstyle + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane2 { " + "position:absolute; " + "visibility:%STR; " + "left:-1px; " + "top:-1px; " + "width:"ht_flex_format"; " + "z-index:%INT; " + "}\n", + id, + (is_enabled) ? "inherit" : "hidden", + ht_flex_w(w - 3, tree), + z + 1 + ); + htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane3 { " + "visibility:%STR; " + "position:absolute; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "z-index:%INT; " + "}\n", + id, + (is_enabled) ? "hidden" : "inherit", + ht_flex_w(w - 3, tree), + z + 1 + ); if(!strcmp(type,"text")) { diff --git a/centrallix/htmlgen/htdrv_calendar.c b/centrallix/htmlgen/htdrv_calendar.c index 3605135fd..f871ffd1d 100644 --- a/centrallix/htmlgen/htdrv_calendar.c +++ b/centrallix/htmlgen/htdrv_calendar.c @@ -141,7 +141,18 @@ htcaRender(pHtSession s, pWgtrNode tree, int z) strtcpy(name,ptr,sizeof(name)); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#ca%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); + htrAddStylesheetItem_va(s, + "\t\t#ca%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, x, y, w, h, z + ); /** Script include to get functions **/ htrAddScriptInclude(s, "/sys/js/htdrv_calendar.js", 0); diff --git a/centrallix/htmlgen/htdrv_chart.c b/centrallix/htmlgen/htdrv_chart.c index d421643cf..1f4a40eb4 100644 --- a/centrallix/htmlgen/htdrv_chart.c +++ b/centrallix/htmlgen/htdrv_chart.c @@ -335,7 +335,7 @@ htchtGenHTML(pHtSession session, pWgtrNode tree, int z) /** Write style rules for the container div. **/ htrAddStylesheetItem_va(session, - "\t#%STR&SYMdiv { " + "\t\t#%STR&SYMdiv { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_checkbox.c b/centrallix/htmlgen/htdrv_checkbox.c index d5f164716..dbc054e83 100644 --- a/centrallix/htmlgen/htdrv_checkbox.c +++ b/centrallix/htmlgen/htdrv_checkbox.c @@ -93,7 +93,7 @@ int htcbRender(pHtSession s, pWgtrNode tree, int z) { /** Write style header. **/ htrAddStylesheetItem_va(s, - "\t#cb%POSmain { " + "\t\t#cb%POSmain { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_clock.c b/centrallix/htmlgen/htdrv_clock.c index 4799127c0..5a4cfd954 100644 --- a/centrallix/htmlgen/htdrv_clock.c +++ b/centrallix/htmlgen/htdrv_clock.c @@ -157,7 +157,7 @@ htclRender(pHtSession s, pWgtrNode tree, int z) /** Write style headers. **/ htrAddStylesheetItem_va(s, - "\t#cl%POSbase { " + "\t\t#cl%POSbase { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " @@ -172,7 +172,7 @@ htclRender(pHtSession s, pWgtrNode tree, int z) z ); htrAddStylesheetItem_va(s, - "\t.cl%POScon { " + "\t\t.cl%POScon { " "position:absolute; " "left:0px; " "top:0px; " @@ -180,8 +180,6 @@ htclRender(pHtSession s, pWgtrNode tree, int z) "z-index:%POS; " "}\n", id, - 0, - 0, z + 2 ); diff --git a/centrallix/htmlgen/htdrv_component.c b/centrallix/htmlgen/htdrv_component.c index c1460c2cf..6fe5c9a2d 100644 --- a/centrallix/htmlgen/htdrv_component.c +++ b/centrallix/htmlgen/htdrv_component.c @@ -286,7 +286,7 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Write styles for the enclosing div. **/ htrAddStylesheetItem_va(s, - "\t#cmp%POSbase { " + "\t\t#cmp%POSbase { " "position:absolute; " "visibility:inherit; " "overflow:visible; " @@ -306,10 +306,10 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Write enclosing div event CSS. **/ htrAddStylesheetItem_va(s, - "\t#cmp%POSbase { " + "\t\t#cmp%POSbase { " "pointer-events:none; " "}\n" - "\t#cmp%POSbase > * { " + "\t\t#cmp%POSbase > * { " "pointer-events:auto; " "}\n", id, id @@ -489,7 +489,18 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) /** Dynamic mode -- load from client **/ htrAddWgtrCtrLinkage(s, tree, "_parentctr"); htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "cmp%POS", id, NULL, ""); - htrAddStylesheetItem_va(s,"\t#cmp%POS { position:absolute; visibility:hidden; left:0px; top:0px; width:0px; height:0px; z-index:0;}\n", id); + htrAddStylesheetItem_va(s, + "\t\t#cmp%POS { " + "position:absolute; " + "visibility:hidden; " + "left:0px; " + "top:0px; " + "width:0px; " + "height:0px; " + "z-index:0; " + "}\n", + id + ); } htrRenderSubwidgets(s, tree, z+1); diff --git a/centrallix/htmlgen/htdrv_datetime.c b/centrallix/htmlgen/htdrv_datetime.c index 2161cda7b..9d50625cc 100644 --- a/centrallix/htmlgen/htdrv_datetime.c +++ b/centrallix/htmlgen/htdrv_datetime.c @@ -205,10 +205,10 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) /** Write style headers. **/ htrAddStylesheetItem_va(s, - "\t#dt%POSbtn { " - "overflow:hidden; " + "\t\t#dt%POSbtn { " "position:absolute; " "visibility:inherit; " + "overflow:hidden; " "left:"ht_flex_format"; " "top:"ht_flex_format"; " "width:"ht_flex_format"; " @@ -227,9 +227,9 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) bgcolor ); htrAddStylesheetItem_va(s, - "\t.dt%POScon { " - "overflow:hidden; " + "\t\t.dt%POScon { " "position:absolute; " + "overflow:hidden; " "left:1px; " "top:1px; " "width:calc(100%% - 20px); " diff --git a/centrallix/htmlgen/htdrv_dropdown.c b/centrallix/htmlgen/htdrv_dropdown.c index 8f3d586ec..965ce6f30 100644 --- a/centrallix/htmlgen/htdrv_dropdown.c +++ b/centrallix/htmlgen/htdrv_dropdown.c @@ -153,15 +153,15 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { /** Write basic element CSS. **/ htrAddStylesheetItem_va(s, - "\t#dd%POSbtn { " - "OVERFLOW:hidden; " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "HEIGHT:"ht_flex_format"; " - "Z-INDEX:%POS; " + "\t\t#dd%POSbtn { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " "cursor:default; " "background-color: %STR&CSSVAL; " "border:1px outset #e0e0e0; " @@ -174,21 +174,28 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { z, bgstr ); - if (*textcolor) { - htrAddStylesheetItem_va(s,"\t#dd%POSbtn { color: %STR&CSSVAL; }\n",id,textcolor); - } + if (*textcolor) + { + htrAddStylesheetItem_va(s, + "\t\t#dd%POSbtn { " + "color:%STR&CSSVAL; " + "}\n", + id, + textcolor + ); + } htrAddStylesheetItem_va(s, - "\t.dd%POScon { " - "overflow:hidden; " + "\t\t.dd%POScon { " "position:absolute; " + "overflow:hidden; " "left:1px; " "top:1px; " "width:1024px; " - "height:"ht_flex_format"; " + "height:%POS; " "z-index:%POS; " "}\n", id, - ht_flex(h - 2, h, 0.0), + h - 2, z + 1 ); diff --git a/centrallix/htmlgen/htdrv_editbox.c b/centrallix/htmlgen/htdrv_editbox.c index f4b85c397..5655e273a 100644 --- a/centrallix/htmlgen/htdrv_editbox.c +++ b/centrallix/htmlgen/htdrv_editbox.c @@ -151,7 +151,7 @@ htebRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ const int base_w = w - (2 * box_offset); htrAddStylesheetItem_va(s, - "\t#eb%POSbase { " + "\t\t#eb%POSbase { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -167,7 +167,7 @@ htebRender(pHtSession s, pWgtrNode tree, int z) z ); htrAddStylesheetItem_va(s, - "\t#eb%POScon1 { " + "\t\t#eb%POScon1 { " "position:absolute; " "visibility:inherit; " "left:5px; " @@ -230,14 +230,40 @@ htebRender(pHtSession s, pWgtrNode tree, int z) /** Use CSS border for drawing **/ if (is_raised) - htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: white gray gray white; %STR }\n",id, main_bg); + { + htrAddStylesheetItem_va(s, + "\t\t#eb%POSbase { " + "border-style:solid; " + "border-width:1px; " + "border-color: " + "white gray gray white; " + "%STR " + "}\n", + id, + main_bg + ); + } else - htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: gray white white gray; %STR }\n",id, main_bg); + { + htrAddStylesheetItem_va(s, + "\t\t#eb%POSbase { " + "border-style:solid; " + "border-width:1px; " + "border-color:gray white white gray; " + "%STR " + "}\n", + id, + main_bg + ); + } if (h >= 0) { htrAddStylesheetItem_va(s, - "\t#eb%POSbase { height:"ht_flex_format"; }\n", - id, ht_flex(h - (2 * box_offset), ht_get_parent_h(tree), ht_get_fl_h(tree)) + "\t\t#eb%POSbase { " + "height:"ht_flex_format"; " + "}\n", + id, + ht_flex_h(h - (2 * box_offset), tree) ); } diff --git a/centrallix/htmlgen/htdrv_fileupload.c b/centrallix/htmlgen/htdrv_fileupload.c index a3ed1a5c2..88953e857 100755 --- a/centrallix/htmlgen/htdrv_fileupload.c +++ b/centrallix/htmlgen/htdrv_fileupload.c @@ -94,7 +94,13 @@ htfuRender(pHtSession s, pWgtrNode tree, int z) htrAddScriptInit_va(s, " fu_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), pane:document.getElementById(\"fu%POSform\"), input:document.getElementById(\"fu%POSinput\"), iframe:document.getElementById(\"fu%POSiframe\"), target:\"%STR&JSSTR\"});\n", name, id, id, id, target); /** style header items **/ - htrAddStylesheetItem_va(s,"#fu%POSbase { POSITION:absolute; VISIBILITY:hidden; }\n", id); + htrAddStylesheetItem_va(s, + "\t\t#fu%POSbase { " + "position:absolute; " + "visibility:hidden; " + "}\n", + id + ); htrAddBodyItem_va(s,"
", id, id, id, id, id, id, id, multiselect?"MULTIPLE":""); /** Check for more sub-widgets **/ diff --git a/centrallix/htmlgen/htdrv_formstatus.c b/centrallix/htmlgen/htdrv_formstatus.c index b4b0d504c..1000a22cd 100644 --- a/centrallix/htmlgen/htdrv_formstatus.c +++ b/centrallix/htmlgen/htdrv_formstatus.c @@ -90,7 +90,7 @@ int htfsRender(pHtSession s, pWgtrNode tree, int z) { /** Ok, write the style header items. **/ htrAddStylesheetItem_va(s, - "\t#fs%POSmain { " + "\t\t#fs%POSmain { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_frameset.c b/centrallix/htmlgen/htdrv_frameset.c index 7ed9215d3..6d88a613a 100644 --- a/centrallix/htmlgen/htdrv_frameset.c +++ b/centrallix/htmlgen/htdrv_frameset.c @@ -58,7 +58,7 @@ htsetRender(pHtSession s, pWgtrNode tree, int z) /** Check for a title. **/ if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) { - htrAddHeaderItem_va(s," %STR&HTE\n",ptr); + htrAddHeaderItem_va(s, "\t%STR&HTE\n", ptr); } /** Loop through the frames (widget/page items) for geometry data **/ diff --git a/centrallix/htmlgen/htdrv_html.c b/centrallix/htmlgen/htdrv_html.c index 509438673..670af981b 100644 --- a/centrallix/htmlgen/htdrv_html.c +++ b/centrallix/htmlgen/htdrv_html.c @@ -103,11 +103,11 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) if (x < 0 || y < 0) { htrAddStylesheetItem_va(s, - "\t#ht%POSpane, #ht%POSpane, #ht%POSfader { " - "POSITION:relative; " - "VISIBILITY:inherit; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "\t\t#ht%POSpane, #ht%POSpane, #ht%POSfader { " + "position:relative; " + "visibility:inherit; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, id, id, ht_flex_w(w, tree), @@ -117,13 +117,13 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) else { htrAddStylesheetItem_va(s, - "\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { " - "POSITION:absolute; " - "VISIBILITY:inherit; " - "LEFT:"ht_flex_format"; " - "TOP:"ht_flex_format"; " - "WIDTH:"ht_flex_format"; " - "Z-INDEX:%POS; " + "\t\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " "}\n", id, id, id, ht_flex_x(x, tree), @@ -135,8 +135,24 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) if (s->Capabilities.CSS1) { - htrAddStylesheetItem_va(s,"\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { overflow:hidden; }\n", id, id, id); - htrAddStylesheetItem_va(s,"\t#ht%POSloader { overflow:hidden; visibility:hidden; position:absolute; top:0px; left:0px; width:0px; height:0px; }\n", id); + htrAddStylesheetItem_va(s, + "\t\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { " + "overflow:hidden; " + "}\n", + id, id, id + ); + htrAddStylesheetItem_va(s, + "\t\t#ht%POSloader { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "top:0px; " + "left:0px; " + "width:0px; " + "height:0px; " + "}\n", + id + ); } /** Write named global **/ diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index b9fe1585f..a1c6e6722 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -56,10 +56,23 @@ htimgSetup(pHtSession s) { /** Global style code for all image widget "img" tags **/ - htrAddStylesheetItem(s, " img.wimage { display:block; position:relative; left:0px; top:0px; }\n"); + htrAddStylesheetItem(s, + "\t\timg.wimage { " + "display:block; " + "position:relative; " + "left:0px; " + "top:0px; " + "}\n" + ); /** Global style code for all image widget "div" containers **/ - htrAddStylesheetItem(s, " div.wimage { visibility:inherit; position:absolute; overflow:hidden; }\n"); + htrAddStylesheetItem(s, + "\t\tdiv.wimage { " + "visibility:inherit; " + "position:absolute; " + "overflow:hidden; " + "}\n" + ); return 0; } @@ -167,7 +180,7 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) /** Write the style for the image container div. **/ htrAddStylesheetItem_va(s, - "\t#img%POS { " + "\t\t#img%POS { " "left:"ht_flex_format"; " "top:"ht_flex_format"; " "width:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_imagebutton.c b/centrallix/htmlgen/htdrv_imagebutton.c index 3f0bb5390..908947e08 100644 --- a/centrallix/htmlgen/htdrv_imagebutton.c +++ b/centrallix/htmlgen/htdrv_imagebutton.c @@ -138,7 +138,7 @@ htibtnRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ htrAddStylesheetItem_va(s, - "\t#ib%POSpane { " + "\t\t#ib%POSpane { " "position:absolute; " "visibility:inherit; " "overflow:hidden;" diff --git a/centrallix/htmlgen/htdrv_label.c b/centrallix/htmlgen/htdrv_label.c index 77ea3ffdc..084bbd97a 100644 --- a/centrallix/htmlgen/htdrv_label.c +++ b/centrallix/htmlgen/htdrv_label.c @@ -198,7 +198,7 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ htrAddStylesheetItem_va(s, - "\t#lbl%POS { " + "\t\t#lbl%POS { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " @@ -233,12 +233,32 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) ); if (is_link) - htrAddStylesheetItem_va(s,"\t#lbl%POS:hover { %[color:%STR&CSSVAL; %]text-decoration:underline; cursor:pointer; }\n", id, *pfgcolor, pfgcolor); + { + htrAddStylesheetItem_va(s, + "\t\t#lbl%POS:hover { " + "%[color:%STR&CSSVAL; %]" + "text-decoration:underline; " + "cursor:pointer; " + "}\n", + id, + (*pfgcolor), pfgcolor + ); + } if (is_link && *cfgcolor) - htrAddStylesheetItem_va(s,"\t#lbl%POS:active { color:%STR&CSSVAL; text-decoration:underline; cursor:pointer; }\n", id, cfgcolor); + { + htrAddStylesheetItem_va(s, + "\t\t#lbl%POS:active { " + "color:%STR&CSSVAL; " + "text-decoration:underline; " + "cursor:pointer; " + "}\n", + id, + cfgcolor + ); + } htrAddStylesheetItem_va(s, - "\t#lbl%POS p { " + "\t\t#lbl%POS p { " "text-align:%STR&CSSVAL; " "%[position:relative; top:50%%; transform:translateY(-50%%); %]" "padding:0px; " diff --git a/centrallix/htmlgen/htdrv_map.c b/centrallix/htmlgen/htdrv_map.c index 73f0d5518..28f23f3f7 100755 --- a/centrallix/htmlgen/htdrv_map.c +++ b/centrallix/htmlgen/htdrv_map.c @@ -104,7 +104,20 @@ int htmapRender(pHtSession s, pWgtrNode map_node, int z) strtcpy(name, ptr, sizeof(name)); /** Add css item for the layer **/ - htrAddStylesheetItem_va(s, "\t#map%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; overflow: hidden; %STR}\n", id, x, y, w, h, z, main_bg); + htrAddStylesheetItem_va(s, + "\t\t#map%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "%STR " + "}\n", + id, x, y, w, h, z, main_bg + ); htrAddWgtrObjLinkage_va(s, map_node, "map%POSbase", id); diff --git a/centrallix/htmlgen/htdrv_menu.c b/centrallix/htmlgen/htdrv_menu.c index 331b8bb92..75188cba8 100644 --- a/centrallix/htmlgen/htdrv_menu.c +++ b/centrallix/htmlgen/htdrv_menu.c @@ -286,7 +286,7 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) /** Write styles for the main DOM element. **/ htrAddStylesheetItem_va(s, - "\t#mn%POSmain { " + "\t\t#mn%POSmain { " "position:absolute; " "visibility:%STR; " "left:"ht_flex_format"; " @@ -305,14 +305,34 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) ); if (shadow_radius > 0) { - htrAddStylesheetItem_va(s,"\t#mn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", id, shadow_offset, shadow_offset, shadow_radius, shadow_color); + htrAddStylesheetItem_va(s, + "\t\t#mn%POSmain { " + "box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; " + "}\n", + id, + shadow_offset, shadow_offset, shadow_radius, shadow_color + ); } if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POSmain { overflow:hidden; border-style: solid; border-width: 1px; border-color: white gray gray white; color:%STR; %STR }\n", id, textcolor, bgstr); + { + htrAddStylesheetItem_va(s, + "\t\t#mn%POSmain { " + "overflow:hidden; " + "border-style:solid; " + "border-width:1px; " + "border-color:white gray gray white; " + "color:%STR; " + "%STR " + "}\n", + id, + textcolor, + bgstr + ); + } /** Write styles for the content container. **/ htrAddStylesheetItem_va(s, - "\t#mn%POScontent { " + "\t\t#mn%POScontent { " "position:absolute; " "visibility:inherit; " "left:0px; " @@ -325,12 +345,38 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) z + 1 ); if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POScontent { overflow:hidden; cursor:default; }\n", id ); + { + htrAddStylesheetItem_va(s, + "\t\t#mn%POScontent { " + "overflow:hidden; " + "cursor:default; " + "}\n", + id + ); + } /** Write styles for the highlight bar. **/ - htrAddStylesheetItem_va(s, "\t#mn%POShigh { POSITION:absolute; VISIBILITY: hidden; LEFT:0px; TOP:0px; Z-INDEX:%POS; }\n", id, z); + htrAddStylesheetItem_va(s, + "\t\t#mn%POShigh { " + "position:absolute; " + "visibility: " + "hidden; " + "left:0px; " + "top:0px; " + "z-index:%POS; " + "}\n", + id, + z + ); if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POShigh { overflow:hidden; }\n", id ); + { + htrAddStylesheetItem_va(s, + "\t\t#mn%POShigh { " + "overflow:hidden; " + "}\n", + id + ); + } /** Get name **/ if (wgtrGetPropertyValue(menu,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; diff --git a/centrallix/htmlgen/htdrv_multiscroll.c b/centrallix/htmlgen/htdrv_multiscroll.c index 2db7a3d66..5829fa8be 100644 --- a/centrallix/htmlgen/htdrv_multiscroll.c +++ b/centrallix/htmlgen/htdrv_multiscroll.c @@ -107,12 +107,36 @@ htmsRender(pHtSession s, pWgtrNode tree, int z) /** Ok, write the style header items. **/ if(s->Capabilities.Dom0NS) { - htrAddStylesheetItem_va(s,"\t#ms%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); + htrAddStylesheetItem_va(s, + "\t\t#ms%POSmain { " + "position:absolute; " + "visibility:inherit; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, x, y, w, h, z + ); } else if(s->Capabilities.CSS1) { - htrAddStylesheetItem_va(s,"\t#ms%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); - htrAddStylesheetItem_va(s,"\t#ms%POSmain { %STR}\n",id,main_bg); + htrAddStylesheetItem_va(s, + "\t\t#ms%POSmain { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "%STR " + "}\n", + id, x, y, w, h, z, + main_bg + ); } else { @@ -163,7 +187,20 @@ htmsRender(pHtSession s, pWgtrNode tree, int z) else cy = h - total_h + total_h_accum; total_h_accum += ch; - htrAddStylesheetItem_va(s,"\t#ms%POSpart%POS { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:0px; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%INTpx,%INTpx,0px);}\n",id,i,cy,w,ch,z+1,w,ch); + htrAddStylesheetItem_va(s, + "\t\t#ms%POSpart%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:0px; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "clip:rect(0px, %INTpx, %INTpx, 0px); " + "}\n", + id, i, cy, w, ch, z + 1, w, ch + ); if (wgtrGetPropertyValue(child,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; if (always_visible && ch == 0) { diff --git a/centrallix/htmlgen/htdrv_objcanvas.c b/centrallix/htmlgen/htdrv_objcanvas.c index 5a1150533..bbd88d22e 100644 --- a/centrallix/htmlgen/htdrv_objcanvas.c +++ b/centrallix/htmlgen/htdrv_objcanvas.c @@ -108,7 +108,7 @@ htocRender(pHtSession s, pWgtrNode oc_node, int z) if (s->Capabilities.CSS2) { htrAddStylesheetItem_va(s, - "\t#oc%POSbase { " + "\t\t#oc%POSbase { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -129,7 +129,20 @@ htocRender(pHtSession s, pWgtrNode oc_node, int z) ); } else - htrAddStylesheetItem_va(s,"\t#oc%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; HEIGHT:%POS; Z-INDEX:%POS; }\n",id,x,y,w,h,z); + { + htrAddStylesheetItem_va(s, + "\t\t#oc%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:%INT; " + "top:%INT; " + "width:%POS; " + "height:%POS; " + "z-index:%POS; " + "}\n", + id, x, y, w, h, z + ); + } htrAddWgtrObjLinkage_va(s, oc_node, "oc%POSbase",id); diff --git a/centrallix/htmlgen/htdrv_osrc.c b/centrallix/htmlgen/htdrv_osrc.c index 9ba4bbfd9..5719e7e8e 100644 --- a/centrallix/htmlgen/htdrv_osrc.c +++ b/centrallix/htmlgen/htdrv_osrc.c @@ -250,7 +250,19 @@ htosrcRender(pHtSession s, pWgtrNode tree, int z) htrAddScriptGlobal(s, "osrc_relationships", "[]", 0); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s," #osrc%POSloader { overflow:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:1px; WIDTH:1px; HEIGHT:1px; Z-INDEX:0; }\n",id); + htrAddStylesheetItem_va(s, + "\t\t#osrc%POSloader { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:0px; " + "top:1px; " + "width:1px; " + "height:1px; " + "z-index:0; " + "}\n", + id + ); /** Script initialization call. **/ htrAddScriptInit_va(s," osrc_init({loader:wgtrGetNodeRef(ns,\"%STR&SYM\"), readahead:%INT, scrollahead:%INT, replicasize:%INT, sql:\"%STR&JSSTR\", filter:\"%STR&JSSTR\", baseobj:\"%STR&JSSTR\", name:\"%STR&SYM\", autoquery:%INT, requestupdates:%INT, ind_act:%INT, use_having:%INT, qy_reveal_only:%INT, send_updates:%INT, key_objname:\"%STR&JSSTR\", refresh:%INT});\n", diff --git a/centrallix/htmlgen/htdrv_page.c b/centrallix/htmlgen/htdrv_page.c index e79f7b47e..9b91fe72d 100755 --- a/centrallix/htmlgen/htdrv_page.c +++ b/centrallix/htmlgen/htdrv_page.c @@ -103,13 +103,13 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) /** Page icon? **/ if (wgtrGetPropertyValue(tree, "icon", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddHeaderItem_va(s, " \n",ptr); + htrAddHeaderItem_va(s, "\t\n",ptr); } /** Check for a title. **/ - if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) + if (wgtrGetPropertyValue(tree, "title", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddHeaderItem_va(s, " %STR&HTE\n",ptr); + htrAddHeaderItem_va(s, "\t%STR&HTE\n", ptr); } /** Check for page load status **/ @@ -324,60 +324,62 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) /** Shutdown **/ htrAddScriptCleanup_va(s, " pg_cleanup();\n"); + /** Add focus box. **/ if(s->Capabilities.HTML40) { - /** Add focus box **/ - htrAddStylesheetItem(s,"\ttd img { display: block; }\n"); - htrAddStylesheetItem(s,"\t#pgtop { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pglft { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgtvl { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:0px;WIDTH:1px;HEIGHT:1px; Z-INDEX:0; }\n"); - htrAddStylesheetItem(s,"\t#pgktop { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgkbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgkrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgklft { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pginpt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:20px; Z-INDEX:20; }\n"); - htrAddStylesheetItem(s,"\t#pgping { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n"); - htrAddStylesheetItem(s,"\t#pgmsg { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n"); + htrAddStylesheetItem(s, + "\t\ttd img { display: block; }\n" + "\t\t#pgtop { position:absolute; visibility:hidden; left:-1000px; top:0px; width:1152px; height:1px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgbtm { position:absolute; visibility:hidden; left:-1000px; top:0px; width:1152px; height:1px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgrgt { position:absolute; visibility:hidden; left:0px; top:-1000px; width:1px; height:864px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pglft { position:absolute; visibility:hidden; left:0px; top:-1000px; width:1px; height:864px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgtvl { position:absolute; visibility:hidden; left:0px; top:0px; width:1px; height:1px; z-index:0; }\n" + "\t\t#pgktop { position:absolute; visibility:hidden; left:-1000px; top:0px; width:1152px; height:1px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgkbtm { position:absolute; visibility:hidden; left:-1000px; top:0px; width:1152px; height:1px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgkrgt { position:absolute; visibility:hidden; left:0px; top:-1000px; width:1px; height:864px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pgklft { position:absolute; visibility:hidden; left:0px; top:-1000px; width:1px; height:864px; z-index:1000; clip:rect(0px,0px,0px,0px); overflow:hidden; }\n" + "\t\t#pginpt { position:absolute; visibility:hidden; left:0px; top:20px; z-index:20; }\n" + "\t\t#pgping { position:absolute; visibility:hidden; left:0px; top:0px; width:0px; height:0px; z-index:0; }\n" + "\t\t#pgmsg { position:absolute; visibility:hidden; left:0px; top:0px; width:0px; height:0px; z-index:0; }\n" + ); } else { - /** Add focus box **/ htrAddStylesheetItem(s, - "\t#pgtop { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pglft { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgtvl { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:1; Z-INDEX:0; }\n" - "\t#pgktop { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgkbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgkrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgklft { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pginpt { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:20; Z-INDEX:20; }\n" - "\t#pgping { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; Z-INDEX:0; }\n" - "\t#pgmsg { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; Z-INDEX:0; }\n"); + "\t\t#pgtop { position:absolute; visibility:hidden; left:0; top:0; width:1152; height:1; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgbtm { position:absolute; visibility:hidden; left:0; top:0; width:1152; height:1; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgrgt { position:absolute; visibility:hidden; left:0; top:0; width:1; height:864; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pglft { position:absolute; visibility:hidden; left:0; top:0; width:1; height:864; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgtvl { position:absolute; visibility:hidden; left:0; top:0; width:1; height:1; z-index:0; }\n" + "\t\t#pgktop { position:absolute; visibility:hidden; left:0; top:0; width:1152; height:1; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgkbtm { position:absolute; visibility:hidden; left:0; top:0; width:1152; height:1; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgkrgt { position:absolute; visibility:hidden; left:0; top:0; width:1; height:864; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pgklft { position:absolute; visibility:hidden; left:0; top:0; width:1; height:864; z-index:1000; clip:rect(1,1); }\n" + "\t\t#pginpt { position:absolute; visibility:hidden; left:0; top:20; z-index:20; }\n" + "\t\t#pgping { position:absolute; visibility:hidden; left:0; top:0; z-index:0; }\n" + "\t\t#pgmsg { position:absolute; visibility:hidden; left:0; top:0; z-index:0; }\n" + ); } if (show == 1) { - htrAddStylesheetItem(s, "\t#pgstat { POSITION:absolute; VISIBILITY:visible; LEFT:0;TOP:0;WIDTH:100%;HEIGHT:99%; Z-INDEX:100000;}\n"); + htrAddStylesheetItem(s, "\t\t#pgstat { position:absolute; visibility:visible; left:0;top:0;width:100%;height:99%; z-index:100000;}\n"); htrAddBodyItemLayerStart(s,0,"pgstat",0, NULL); htrAddBodyItem_va(s, "", bgstr); htrAddBodyItem (s, "
\n"); htrAddBodyItemLayerEnd(s,0); } - htrAddStylesheetItem_va(s, "\thtml { overflow:hidden; }\n"); - htrAddStylesheetItem_va(s, "\tbody { overflow:hidden; %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", + htrAddStylesheetItem_va(s, "\t\thtml { overflow:hidden; }\n"); + htrAddStylesheetItem_va(s, "\t\tbody { overflow:hidden; %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", font_size > 0, font_size, *font_name, font_name); - htrAddStylesheetItem(s, "\tpre { font-size:90%; }\n"); + htrAddStylesheetItem(s, "\t\tpre { font-size:90%; }\n"); if (s->Capabilities.Dom0NS) { - htrAddStylesheetItem_va(s, "\ttd { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", + htrAddStylesheetItem_va(s, "\t\ttd { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", font_size > 0, font_size, *font_name, font_name); - htrAddStylesheetItem_va(s, "\tfont { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", + htrAddStylesheetItem_va(s, "\t\tfont { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", font_size > 0, font_size, *font_name, font_name); } diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 6536a9689..ef64ab7b6 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -162,7 +162,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) { offset = -2 * box_offset; htrAddStylesheetItem_va(s, - "\t#pn%POSmain {" + "\t\t#pn%POSmain {" "border-style: solid; " "border-width: 1px; " "border-color: %STR %STR %STR %STR; " @@ -175,7 +175,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) { offset = -2 * box_offset; htrAddStylesheetItem_va(s, - "\t#pn%POSmain {" + "\t\t#pn%POSmain {" "border-style: solid;" "border-width: 1px; " "border-color:%STR&CSSVAL; " @@ -191,7 +191,7 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) /** Write the main CSS for the pane DOM node. **/ htrAddStylesheetItem_va(s, - "\t#pn%POSmain {" + "\t\t#pn%POSmain {" "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -216,8 +216,11 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) if (shadow_radius > 0) { htrAddStylesheetItem_va(s, - "\t#pn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", - id, shadow_offset, shadow_offset, shadow_radius, shadow_color + "\t\t#pn%POSmain { " + "box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; " + "}\n", + id, + shadow_offset, shadow_offset, shadow_radius, shadow_color ); } diff --git a/centrallix/htmlgen/htdrv_radiobutton.c b/centrallix/htmlgen/htdrv_radiobutton.c index 27024d363..c201a0a16 100644 --- a/centrallix/htmlgen/htdrv_radiobutton.c +++ b/centrallix/htmlgen/htdrv_radiobutton.c @@ -185,7 +185,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) const int para_height = s->ClientInfo->ParagraphHeight; const int top_offset = (para_height * 3) / 4 + 1; htrAddStylesheetItem_va(s, - "#rb%POSparent { " + "\t\t#rb%POSparent { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -203,7 +203,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) z ); htrAddStylesheetItem_va(s, - "#rb%POSborder { " + "\t\t#rb%POSborder { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -219,7 +219,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) z + 1 ); htrAddStylesheetItem_va(s, - "#rb%POScover { " + "\t\t#rb%POScover { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -233,7 +233,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) z + 2 ); htrAddStylesheetItem_va(s, - "#rb%POStitle { " + "\t\t#rb%POStitle { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -319,7 +319,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) const double percent_space_above = (100.0 / radio_buttons.nItems) * i; const double content_above = ((double)content_height / radio_buttons.nItems) * i; htrAddStylesheetItem_va(s, - "#rb%POSoption%POS { " + "\t\t#rb%POSoption%POS { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " @@ -341,7 +341,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) /** Write CSS for the radio button elements. **/ htrAddStylesheetItem_va(s, - "#rb%POSbuttonset%POS, " + "\t\t#rb%POSbuttonset%POS, " "#rb%POSbuttonunset%POS { " "position:absolute; " "overflow:hidden; " @@ -358,7 +358,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) z + 2 ); htrAddStylesheetItem_va(s, - "#rb%POSvalue%POS { " + "\t\t#rb%POSvalue%POS { " "position:absolute; " "visibility:hidden; " "overflow:hidden; " @@ -372,7 +372,7 @@ int htrbRender(pHtSession s, pWgtrNode tree, int z) z + 2 ); htrAddStylesheetItem_va(s, - "#rb%POSlabel%POS { " + "\t\t#rb%POSlabel%POS { " "position:absolute; " "visibility:inherit; " "overflow:hidden; " diff --git a/centrallix/htmlgen/htdrv_scrollbar.c b/centrallix/htmlgen/htdrv_scrollbar.c index e9b5b65e4..5b304443e 100644 --- a/centrallix/htmlgen/htdrv_scrollbar.c +++ b/centrallix/htmlgen/htdrv_scrollbar.c @@ -163,11 +163,48 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) } /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#sb%POSpane { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; clip:rect(0px,%POSpx,%POSpx,0px); Z-INDEX:%POS; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); + htrAddStylesheetItem_va(s, + "\t\t#sb%POSpane { " + "position:absolute; " + "visibility:%STR; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + x, y, w, h, + z + ); if (is_horizontal) - htrAddStylesheetItem_va(s,"\t#sb%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:18px; TOP:0px; WIDTH:18px; Z-INDEX:%POS; }\n",id,z+1); + htrAddStylesheetItem_va(s, + "\t\t#sb%POSthum { " + "position:absolute; " + "visibility:inherit; " + "left:18px; " + "top:0px; " + "width:18px; " + "z-index:%POS; " + "}\n", + id, + z + 1 + ); else - htrAddStylesheetItem_va(s,"\t#sb%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:18px; WIDTH:18px; Z-INDEX:%POS; }\n",id,z+1); + htrAddStylesheetItem_va(s, + "\t\t#sb%POSthum { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:18px; " + "width:18px; " + "z-index:%POS; " + "}\n", + id, + z + 1 + ); /** Write globals for internal use **/ htrAddScriptGlobal(s, "sb_target_img", "null", 0); diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index 7b012e95e..84e98fadb 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -167,7 +167,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) /** Write shared CSS for the following UI elements. **/ htrAddStylesheetItem_va(s, - "\t.sp%POS_scroll { " + "\t\t.sp%POS_scroll { " "position:absolute; " "left:"ht_flex_format"; " "}\n", @@ -267,14 +267,14 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) { /** Write CSS for everything. **/ htrAddStylesheetItem_va(s, - "\t#sp%POSpane { " + "\t\t#sp%POSpane { " "position:absolute; " "visibility:%STR; " + "overflow:hidden; " "left:"ht_flex_format"; " "top:"ht_flex_format"; " "width:"ht_flex_format"; " "height:"ht_flex_format"; " - "overflow:clip; " "z-index:%POS; " "}\n", id, @@ -286,7 +286,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) z ); htrAddStylesheetItem_va(s, - "\t#sp%POSarea { " + "\t\t#sp%POSarea { " "position:absolute; " "visibility:inherit; " "left:0px; " @@ -299,7 +299,7 @@ htspaneRender(pHtSession s, pWgtrNode tree, int z) z + 1 ); htrAddStylesheetItem_va(s, - "\t#sp%POSthumb { " + "\t\t#sp%POSthumb { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_spinner.c b/centrallix/htmlgen/htdrv_spinner.c index 7252b86a5..fcb0a7776 100644 --- a/centrallix/htmlgen/htdrv_spinner.c +++ b/centrallix/htmlgen/htdrv_spinner.c @@ -114,12 +114,12 @@ htspnrRender(pHtSession s, pWgtrNode tree, int z) } /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#spnr%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#spnr%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-12,z); - htrAddStylesheetItem_va(s,"\t#spnr%POScon1 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-2-12,z+1); - htrAddStylesheetItem_va(s,"\t#spnr%POScon2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-2-12,z+1); - htrAddStylesheetItem_va(s,"\t#spnr_button_up { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",1+w-12,1,w,z); - htrAddStylesheetItem_va(s,"\t#spnr_button_down { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",1+w-12,1+9,w,z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POSmain { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, x, y, w, z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POSbase { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-12, z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POScon1 { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-2-12, z+1); + htrAddStylesheetItem_va(s, "\t\t#spnr%POScon2 { position:absolute; visibility:hidden; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-2-12, z+1); + htrAddStylesheetItem_va(s, "\t\t#spnr_button_up { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", 1+w-12, 1, w, z); + htrAddStylesheetItem_va(s, "\t\t#spnr_button_down { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", 1+w-12, 1+9,w, z); /** DOM Linkage **/ htrAddWgtrObjLinkage_va(s, tree, "spnr%POSmain",id); diff --git a/centrallix/htmlgen/htdrv_tab.c b/centrallix/htmlgen/htdrv_tab.c index 75eaefdee..ba6e02a0f 100644 --- a/centrallix/htmlgen/htdrv_tab.c +++ b/centrallix/htmlgen/htdrv_tab.c @@ -470,14 +470,14 @@ httabRender(pHtSession s, pWgtrNode tree, int z) int tab_x = (x + xtoffset) + (i_offset_x * i); int tab_y = (y + ytoffset) + (i_offset_y * i); htrAddStylesheetItem_va(s, - "\t#tc%POStab%POS { " + "\t\t#tc%POStab%POS { " "position:absolute; " "visibility:inherit; " + "overflow:hidden; " "left:"ht_flex_format"; " "top:"ht_flex_format"; " "%[width:%POSpx; %]" /* Tab width has 0 flexibility. */ "%[height:%POSpx; %]" /* Tab height has 0 flexibility. */ - "overflow:hidden; " "z-index:%POS; " "cursor:default; " "border-radius:" @@ -517,8 +517,11 @@ httabRender(pHtSession s, pWgtrNode tree, int z) ); htrAddStylesheetItem_va(s, - "\t#tc%POStab%POS.tab_selected { transform: translate(%INTpx, %INTpx); }\n", - id, i + 1, select_x_offset, select_y_offset + "\t\t#tc%POStab%POS.tab_selected { " + "transform:translate(%INTpx, %INTpx); " + "}\n", + id, i + 1, + select_x_offset, select_y_offset ); /** Write tab HTML content. **/ @@ -544,7 +547,7 @@ httabRender(pHtSession s, pWgtrNode tree, int z) /** Write tab control CSS and HTML. **/ htrAddStylesheetItem_va(s, - "#tc%POSctrl {" + "\t\t#tc%POSctrl {" "position:absolute; " "overflow:hidden; " "left:"ht_flex_format"; " diff --git a/centrallix/htmlgen/htdrv_table.c b/centrallix/htmlgen/htdrv_table.c index 5876bf658..a7475d3a2 100644 --- a/centrallix/htmlgen/htdrv_table.c +++ b/centrallix/htmlgen/htdrv_table.c @@ -145,7 +145,7 @@ httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) /** Write CSS for the table base element. **/ const int content_width = (t->overlap_scrollbar) ? (t->w) : (t->w - 18); htrAddStylesheetItem_va(s, - "\t#tbld%POSbase { " + "\t\t#tbld%POSbase { " "position:absolute; " "visibility:inherit; " "left:"ht_flex_format"; " @@ -165,7 +165,7 @@ httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) /** Write CSS for the table scrollbar. **/ const int row_start_y = (t->has_header) ? (t->min_rowheight + t->cellvspacing) : 0; htrAddStylesheetItem_va(s, - "\t#tbld%POSscroll { " + "\t\t#tbld%POSscroll { " "position:absolute; " "visibility:%STR; " "left:"ht_flex_format"; " @@ -184,7 +184,7 @@ httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) /** Write CSS for the table scroll thumb. **/ htrAddStylesheetItem_va(s, - "\t#tbld%POSthumb { " + "\t\t#tbld%POSthumb { " "position:absolute; " "visibility:inherit; " "left:0px; " @@ -366,7 +366,7 @@ httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) int h; if (wgtrGetPropertyValue(sub_tree, "height", DATA_T_INTEGER, POD(&h)) != 0) h = t->min_rowheight; htrAddStylesheetItem_va(s, - "\t#tbld%POSsub%POS { " + "\t\t#tbld%POSsub%POS { " "position:absolute; " "visibility:hidden; " "left:0px; " diff --git a/centrallix/htmlgen/htdrv_terminal.c b/centrallix/htmlgen/htdrv_terminal.c index a4a8ec44a..77b01da00 100644 --- a/centrallix/htmlgen/htdrv_terminal.c +++ b/centrallix/htmlgen/htdrv_terminal.c @@ -144,10 +144,24 @@ httermRender(pHtSession s, pWgtrNode tree, int z) htrAddBodyItem_va(s," \n",id); /** write the stylesheet header element **/ - htrAddStylesheetItem_va(s," #term%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; HEIGHT:%POS; Z-INDEX:%POS; }\n",id,x,y,cols*fontsize,rows*fontsize,z); - htrAddStylesheetItem_va(s," #term%POSreader { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; WIDTH:1; HEIGHT:1; Z-INDEX:-20; }\n",id); - htrAddStylesheetItem_va(s," #term%POSwriter { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; WIDTH:1; HEIGHT:1; Z-INDEX:-20; }\n",id); - htrAddStylesheetItem_va(s," .fixed%POS {font-family: fixed; }\n",id); + htrAddStylesheetItem_va(s, + "\t\t#term%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POS; " + "z-index:%POS; " + "}\n", + id, x, y, + cols * fontsize, + rows * fontsize, + z + ); + htrAddStylesheetItem_va(s, "\t\t#term%POSreader { position:absolute; visibility:hidden; left:0px; top:0px; width:1px; height:1; z-index:-20; }\n", id); + htrAddStylesheetItem_va(s, "\t\t#term%POSwriter { position:absolute; visibility:hidden; left:0px; top:0px; width:1px; height:1; z-index:-20; }\n", id); + htrAddStylesheetItem_va(s, "\t\t.fixed%POS {font-family: fixed; }\n",id); /** init line **/ htrAddScriptInit_va(s," terminal_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), rdr:\"term%POSreader\", wtr:\"term%POSwriter\", fxd:\"fixed%POS\", source:'%STR&JSSTR', rows:%INT, cols:%INT, colors:new Array(", diff --git a/centrallix/htmlgen/htdrv_textarea.c b/centrallix/htmlgen/htdrv_textarea.c index bd10c2efb..66884fd5c 100644 --- a/centrallix/htmlgen/htdrv_textarea.c +++ b/centrallix/htmlgen/htdrv_textarea.c @@ -143,7 +143,6 @@ httxRender(pHtSession s, pWgtrNode tree, int z) x, y, w-2*box_offset, h-2*box_offset, z, "", (char*[]){"border_color","#e0e0e0", "border_style",(is_raised?"outset":"inset"), NULL}, "overflow:hidden; position:absolute;"); - //htrAddStylesheetItem_va(s,"\t#tx%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%INT; overflow:hidden; }\n",id,x,y,w-2*box_offset,z); /** DOM Linkage **/ htrAddWgtrObjLinkage_va(s, tree, "tx%POSbase",id); @@ -171,14 +170,6 @@ httxRender(pHtSession s, pWgtrNode tree, int z) /** HTML body
element for the base layer. **/ htrAddBodyItem_va(s, "