Newer
Older
Eric Oulashin
committed
19002
19003
19004
19005
19006
19007
19008
19009
19010
19011
19012
19013
19014
19015
19016
19017
19018
19019
19020
19021
19022
19023
19024
19025
19026
19027
19028
19029
19030
19031
19032
19033
19034
19035
19036
19037
19038
19039
// Returns whether a number is a valid scan mode
//
// Parameters:
// pNum: A number to test
//
// Return value: Boolean - Whether or not the given number is a valid scan mode
function isValidScanMode(pNum)
{
if (typeof(pNum) !== "number")
return false;
// The scan modes are defined in sbbsdefs.js
var validScanModes = [SCAN_READ, SCAN_CONST, SCAN_NEW, SCAN_BACK, SCAN_TOYOU,
SCAN_FIND, SCAN_UNREAD, SCAN_MSGSONLY, SCAN_POLLS, SCAN_INDEX];
var numIsValidScanMode = false;
for (var i = 0; i < validScanModes.length && !numIsValidScanMode; ++i)
numIsValidScanMode = (pNum === validScanModes[i]);
return numIsValidScanMode;
}
// Returns whether a user number is valid (only an actual, active user)
//
// Parameters:
// pUserNum: A user number
//
// Return value: Boolean - Whether or not the given user number is valid
function isValidUserNum(pUserNum)
{
if (typeof(pUserNum) !== "number")
return false;
if (pUserNum < 1 || pUserNum > system.lastuser)
return false;
var userIsValid = false;
var theUser = new User(pUserNum);
if (theUser != null && (theUser.settings & USER_DELETED) == 0 && (theUser.settings & USER_INACTIVE) == 0)
userIsValid = true;
return userIsValid;
}
19041
19042
19043
19044
19045
19046
19047
19048
19049
19050
19051
19052
19053
19054
19055
19056
19057
19058
19059
19060
19061
19062
19063
19064
19065
19066
19067
19068
19069
19070
19071
19072
19073
19074
19075
19076
19077
19078
19079
19080
19081
19082
19083
19084
19085
19086
19087
19088
19089
19090
19091
19092
19093
19094
19095
19096
19097
19098
19099
19100
19101
19102
19103
19104
19105
19106
19107
19108
19109
19110
19111
19112
19113
19114
19115
19116
19117
19118
19119
19120
19121
19122
19123
19124
19125
19126
19127
19128
19129
19130
19131
19132
19133
19134
19135
19136
19137
19138
19139
19140
19141
19142
19143
19144
19145
19146
19147
19148
19149
19150
19151
19152
19153
19154
19155
19156
19157
19158
19159
19160
19161
19162
19163
19164
// Returns the index of the last ANSI code in a string.
//
// Parameters:
// pStr: The string to search in
// pANSIRegexes: An array of regular expressions to use for searching for ANSI codes
//
// Return value: The index of the last ANSI code in the string, or -1 if not found
function idxOfLastANSICode(pStr, pANSIRegexes)
{
var lastANSIIdx = -1;
for (var i = 0; i < pANSIRegexes.length; ++i)
{
var lastANSIIdxTmp = regexLastIndexOf(pStr, pANSIRegexes[i]);
if (lastANSIIdxTmp > lastANSIIdx)
lastANSIIdx = lastANSIIdxTmp;
}
return lastANSIIdx;
}
// Returns the index of the first ANSI code in a string.
//
// Parameters:
// pStr: The string to search in
// pANSIRegexes: An array of regular expressions to use for searching for ANSI codes
//
// Return value: The index of the first ANSI code in the string, or -1 if not found
function idxOfFirstANSICode(pStr, pANSIRegexes)
{
var firstANSIIdx = -1;
for (var i = 0; i < pANSIRegexes.length; ++i)
{
var firstANSIIdxTmp = regexFirstIndexOf(pStr, pANSIRegexes[i]);
if (firstANSIIdxTmp > firstANSIIdx)
firstANSIIdx = firstANSIIdxTmp;
}
return firstANSIIdx;
}
// Returns the number of times an ANSI code is matched in a string.
//
// Parameters:
// pStr: The string to search in
// pANSIRegexes: An array of regular expressions to use for searching for ANSI codes
//
// Return value: The number of ANSI code matches in the string
function countANSICodes(pStr, pANSIRegexes)
{
var ANSICount = 0;
for (var i = 0; i < pANSIRegexes.length; ++i)
{
var matches = pStr.match(pANSIRegexes[i]);
if (matches != null)
ANSICount += matches.length;
}
return ANSICount;
}
// Removes ANSI codes from a string.
//
// Parameters:
// pStr: The string to remove ANSI codes from
// pANSIRegexes: An array of regular expressions to use for searching for ANSI codes
//
// Return value: A version of the string without ANSI codes
function removeANSIFromStr(pStr, pANSIRegexes)
{
if (typeof(pStr) != "string")
return "";
var theStr = pStr;
for (var i = 0; i < pANSIRegexes.length; ++i)
theStr = theStr.replace(pANSIRegexes[i], "");
return theStr;
}
// Returns the last index in a string where a regex is found.
// From this page:
// http://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expr
//
// Parameters:
// pStr: The string to search
// pRegex: The regular expression to match in the string
// pStartPos: Optional - The starting position in the string. If this is not
// passed, then the end of the string will be used.
//
// Return value: The last index in the string where the regex is found, or -1 if not found.
function regexLastIndexOf(pStr, pRegex, pStartPos)
{
pRegex = (pRegex.global) ? pRegex : new RegExp(pRegex.source, "g" + (pRegex.ignoreCase ? "i" : "") + (pRegex.multiLine ? "m" : ""));
if (typeof(pStartPos) == "undefined")
pStartPos = pStr.length;
else if (pStartPos < 0)
pStartPos = 0;
var stringToWorkWith = pStr.substring(0, pStartPos + 1);
var lastIndexOf = -1;
var nextStop = 0;
while ((result = pRegex.exec(stringToWorkWith)) != null)
{
lastIndexOf = result.index;
pRegex.lastIndex = ++nextStop;
}
return lastIndexOf;
}
// Returns the first index in a string where a regex is found.
//
// Parameters:
// pStr: The string to search
// pRegex: The regular expression to match in the string
//
// Return value: The first index in the string where the regex is found, or -1 if not found.
function regexFirstIndexOf(pStr, pRegex)
{
pRegex = (pRegex.global) ? pRegex : new RegExp(pRegex.source, "g" + (pRegex.ignoreCase ? "i" : "") + (pRegex.multiLine ? "m" : ""));
var indexOfRegex = -1;
var nextStop = 0;
while ((result = pRegex.exec(pStr)) != null)
{
indexOfRegex = result.index;
pRegex.lastIndex = ++nextStop;
}
return indexOfRegex;
}
19165
19166
19167
19168
19169
19170
19171
19172
19173
19174
19175
19176
19177
19178
19179
19180
19181
19182
19183
19184
19185
19186
19187
19188
// Returns whether or not a string has any ASCII drawing characters (typically above ASCII value 127).
//
// Parameters:
// pText: The text to check
//
// Return value: Boolean - Whether or not the text has any ASCII drawing characters
function textHasDrawingChars(pText)
{
if (typeof(pText) !== "string" || pText.length == 0)
return false;
if (typeof(textHasDrawingChars.chars) === "undefined")
{
textHasDrawingChars.chars = [ascii(169), ascii(170)];
for (var asciiVal = 174; asciiVal <= 223; ++asciiVal)
textHasDrawingChars.chars.push(ascii(asciiVal));
textHasDrawingChars.chars.push(ascii(254));
}
var drawingCharsFound = false;
for (var i = 0; i < textHasDrawingChars.chars.length && !drawingCharsFound; ++i)
drawingCharsFound = drawingCharsFound || (pText.indexOf(textHasDrawingChars.chars[i]) > -1);
return drawingCharsFound;
}

Eric Oulashin
committed
19189
19190
19191
19192
19193
19194
19195
19196
19197
19198
19199
19200
19201
19202
19203
19204
19205
19206
// Returns a string with a character repeated a given number of times
//
// Parameters:
// pChar: The character to repeat in the string
// pNumTimes: The number of times to repeat the character
//
// Return value: A string with the given character repeated the given number of times
function charStr(pChar, pNumTimes)
{
if (typeof(pChar) !== "string" || pChar.length == 0 || typeof(pNumTimes) !== "number" || pNumTimes < 1)
return "";
var str = "";
for (var i = 0; i < pNumTimes; ++i)
str += pChar;
return str;
}
// Returns whether the logged-in user can view deleted messages.
function canViewDeletedMsgs()
{
var usersVDM = ((system.settings & SYS_USRVDELM) == SYS_USRVDELM);
var sysopVDM = ((system.settings & SYS_SYSVDELM) == SYS_SYSVDELM);
return (usersVDM || (user.is_sysop && sysopVDM));
}

Eric Oulashin
committed
19215
19216
19217
19218
19219
19220
19221
19222
19223
19224
19225
19226
19227
19228
19229
19230
19231
19232
19233
19234
19235
19236
19237
19238
19239
19240
19241
19242
19243
19244
19245
19246
19247
19248
19249
19250
19251
19252
19253
19254
19255
19256
19257
19258
19259
19260
19261
19262
19263
19264
19265
19266
19267
19268
19269
19270
19271
19272
19273
19274
19275
19276
19277
19278
19279
19280
19281
19282
19283
19284
19285
19286
19287
19288
19289
19290
19291
19292
19293
19294
19295
19296
19297
19298
19299
19300
19301
19302
19303
19304
19305
19306
19307
19308
19309
19310
19311
19312
19313
19314
19315
19316
19317
19318
19319
19320
19321
19322
19323
19324
19325
19326
19327
19328
19329
19330
19331
19332
19333
19334
19335
19336
19337
19338
19339
19340
19341
19342
19343
19344
19345
19346
19347
19348
19349
19350
19351
19352
19353
19354
19355
19356
19357
19358
19359
19360
19361
19362
19363
19364
19365
19366
19367
19368
19369
19370
19371
19372
19373
19374
19375
19376
19377
19378
19379
19380
19381
19382
19383
19384
19385
19386
19387
19388
19389
19390
19391
19392
19393
19394
19395
19396
19397
19398
19399
19400
19401
19402
19403
19404
19405
19406
19407
19408
19409
19410
19411
19412
19413
19414
19415
19416
19417
19418
19419
19420
19421
19422
19423
19424
19425
19426
19427
19428
19429
19430
19431
19432
19433
19434
19435
19436
19437
19438
19439
19440
19441
19442
19443
19444
19445
19446
19447
19448
19449
19450
19451
19452
19453
19454
19455
19456
19457
19458
19459
19460
19461
19462
19463
19464
19465
19466
19467
19468
19469
19470
19471
19472
19473
19474
19475
19476
19477
19478
19479
19480
19481
19482
19483
19484
19485
19486
19487
19488
19489
19490
19491
19492
19493
19494
19495
19496
19497
19498
19499
19500
19501
19502
19503
19504
19505
19506
19507
19508
19509
19510
19511
19512
19513
19514
19515
19516
19517
19518
19519
19520
19521
19522
19523
19524
19525
19526
19527
19528
19529
19530
19531
19532
19533
19534
19535
19536
19537
19538
19539
19540
19541
19542
19543
19544
19545
19546
19547
19548
19549
19550
19551
19552
19553
19554
19555
19556
19557
19558
19559
19560
19561
19562
19563
19564
19565
19566
19567
19568
19569
19570
19571
19572
19573
19574
19575
19576
19577
19578
19579
19580
19581
19582
19583
19584
19585
19586
19587
19588
19589
19590
19591
19592
19593
19594
19595
19596
19597
19598
19599
19600
19601
19602
19603
19604
19605
19606
19607
19608
19609
19610
19611
19612
19613
19614
19615
19616
19617
19618
19619
19620
19621
19622
19623
19624
19625
19626
19627
19628
19629
19630
19631
19632
19633
19634
19635
19636
19637
19638
19639
19640
19641
19642
19643
19644
19645
19646
19647
19648
19649
19650
19651
19652
19653
19654
19655
19656
19657
19658
19659
19660
19661
19662
19663
19664
19665
19666
19667
19668
19669
19670
19671
19672
19673
19674
19675
19676
19677
19678
19679
19680
19681
19682
19683
19684
19685
19686
19687
19688
19689
19690
19691
19692
19693
19694
19695
19696
19697
19698
19699
19700
19701
19702
19703
19704
19705
19706
19707
19708
19709
19710
19711
19712
19713
19714
19715
19716
19717
19718
19719
19720
19721
19722
19723
19724
19725
19726
19727
19728
19729
19730
19731
19732
19733
19734
19735
19736
19737
19738
19739
19740
19741
19742
19743
19744
19745
19746
19747
19748
19749
19750
19751
19752
19753
19754
19755
19756
19757
19758
19759
19760
19761
19762
19763
19764
19765
19766
19767
19768
19769
19770
19771
19772
19773
19774
19775
19776
19777
19778
19779
19780
19781
19782
19783
19784
19785
19786
19787
19788
19789
19790
19791
19792
19793
19794
19795
19796
19797
19798
19799
19800
19801
19802
19803
19804
19805
19806
19807
19808
19809
19810
19811
19812
19813
19814
19815
19816
19817
19818
19819
19820
19821
19822
19823
19824
19825
19826
19827
19828
19829
19830
19831
19832
19833
19834
19835
19836
19837
19838
19839
19840
19841
19842
19843
19844
19845
19846
19847
19848
19849
19850
19851
19852
19853
19854
19855
19856
19857
19858
19859
19860
19861
19862
19863
19864
19865
19866
19867
19868
19869
19870
19871
19872
19873
19874
19875
19876
19877
19878
19879
19880
19881
19882
19883
19884
19885
19886
19887
19888
19889
19890
19891
19892
19893
19894
19895
19896
19897
19898
19899
19900
19901
19902
19903
19904
19905
19906
19907
19908
19909
19910
19911
19912
19913
19914
19915
19916
19917
19918
19919
19920
19921
19922
19923
19924
19925
19926
19927
19928
19929
19930
19931
19932
19933
19934
19935
19936
19937
19938
19939
19940
19941
19942
19943
19944
19945
19946
19947
19948
19949
19950
19951
19952
19953
19954
19955
19956
19957
19958
19959
19960
19961
19962
19963
19964
19965
19966
19967
19968
19969
19970
19971
19972
19973
19974
19975
19976
19977
19978
19979
19980
19981
19982
19983
19984
19985
19986
19987
19988
19989
19990
19991
19992
19993
19994
19995
19996
19997
19998
19999
20000
///////////////////////////////////////////////////////////////////////////////////
// ChoiceScrollbox stuff (this was copied from SlyEdit_Misc.js; maybe there's a better way to do this)
// Returns the minimum width for a ChoiceScrollbox
function ChoiceScrollbox_MinWidth()
{
return 73; // To leave room for the navigation text in the bottom border
}
// ChoiceScrollbox constructor
//
// Parameters:
// pLeftX: The horizontal component (column) of the upper-left coordinate
// pTopY: The vertical component (row) of the upper-left coordinate
// pWidth: The width of the box (including the borders)
// pHeight: The height of the box (including the borders)
// pTopBorderText: The text to include in the top border
// pCfgObj: The script/program configuration object (color settings are used)
// pAddTCharsAroundTopText: Optional, boolean - Whether or not to use left & right T characters
// around the top border text. Defaults to true.
// pReplaceTopTextSpacesWithBorderChars: Optional, boolean - Whether or not to replace
// spaces in the top border text with border characters.
// Defaults to false.
function ChoiceScrollbox(pLeftX, pTopY, pWidth, pHeight, pTopBorderText, pCfgObj,
pAddTCharsAroundTopText, pReplaceTopTextSpacesWithBorderChars)
{
if (pCfgObj == null || typeof(pCfgObj) !== "object")
pCfgObj = {};
if (pCfgObj.colors == null || typeof(pCfgObj.colors) !== "object")
{
pCfgObj.colors = {
listBoxBorder: "\x01n\x01g",
listBoxBorderText: "\x01n\x01b\x01h",
listBoxItemText: "\x01n\x01c",
listBoxItemHighlight: "\x01n\x01" + "4\x01w\x01h"
};
}
else
{
if (!pCfgObj.colors.hasOwnProperty("listBoxBorder"))
pCfgObj.colors.listBoxBorder = "\x01n\x01g";
if (!pCfgObj.colors.hasOwnProperty("listBoxBorderText"))
pCfgObj.colors.listBoxBorderText = "\x01n\x01b\x01h";
if (!pCfgObj.colors.hasOwnProperty("listBoxItemText"))
pCfgObj.colors.listBoxItemText = "\x01n\x01c";
if (!pCfgObj.colors.hasOwnProperty("listBoxItemHighlight"))
pCfgObj.colors.listBoxItemHighlight = "\x01n\x01" + "4\x01w\x01h";
}
// The default is to add left & right T characters around the top border
// text. But also use pAddTCharsAroundTopText if it's a boolean.
var addTopTCharsAroundText = true;
if (typeof(pAddTCharsAroundTopText) == "boolean")
addTopTCharsAroundText = pAddTCharsAroundTopText;
// If pReplaceTopTextSpacesWithBorderChars is true, then replace the spaces
// in pTopBorderText with border characters.
if (pReplaceTopTextSpacesWithBorderChars)
{
var startIdx = 0;
var firstSpcIdx = pTopBorderText.indexOf(" ", 0);
// Look for the first non-space after firstSpaceIdx
var nonSpcIdx = -1;
for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
{
if (pTopBorderText.charAt(i) != " ")
nonSpcIdx = i;
}
var firstStrPart = "";
var lastStrPart = "";
var numSpaces = 0;
while ((firstSpcIdx > -1) && (nonSpcIdx > -1))
{
firstStrPart = pTopBorderText.substr(startIdx, (firstSpcIdx-startIdx));
lastStrPart = pTopBorderText.substr(nonSpcIdx);
numSpaces = nonSpcIdx - firstSpcIdx;
if (numSpaces > 0)
{
pTopBorderText = firstStrPart + "\x01n" + pCfgObj.colors.listBoxBorder;
for (var i = 0; i < numSpaces; ++i)
pTopBorderText += HORIZONTAL_SINGLE;
pTopBorderText += "\x01n" + pCfgObj.colors.listBoxBorderText + lastStrPart;
}
// Look for the next space and non-space character after that.
firstSpcIdx = pTopBorderText.indexOf(" ", nonSpcIdx);
// Look for the first non-space after firstSpaceIdx
nonSpcIdx = -1;
for (var i = firstSpcIdx; (i < pTopBorderText.length) && (nonSpcIdx == -1); ++i)
{
if (pTopBorderText.charAt(i) != " ")
nonSpcIdx = i;
}
}
}
this.programCfgObj = pCfgObj;
var minWidth = ChoiceScrollbox_MinWidth();
this.dimensions = {
topLeftX: pLeftX,
topLeftY: pTopY,
width: 0,
height: pHeight,
bottomRightX: 0,
bottomRightY: 0
};
// Make sure the width is the minimum width
if ((pWidth < 0) || (pWidth < minWidth))
this.dimensions.width = minWidth;
else
this.dimensions.width = pWidth;
this.dimensions.bottomRightX = this.dimensions.topLeftX + this.dimensions.width - 1;
this.dimensions.bottomRightY = this.dimensions.topLeftY + this.dimensions.height - 1;
// The text item array and member variables relating to it and the items
// displayed on the screen during the input loop
this.txtItemList = [];
this.chosenTextItemIndex = -1;
this.topItemIndex = 0;
this.bottomItemIndex = 0;
// Top border string
var innerBorderWidth = this.dimensions.width - 2;
// Calculate the maximum top border text length to account for the left/right
// T chars and "Page #### of ####" text
var maxTopBorderTextLen = innerBorderWidth - (pAddTCharsAroundTopText ? 21 : 19);
if (strip_ctrl(pTopBorderText).length > maxTopBorderTextLen)
pTopBorderText = pTopBorderText.substr(0, maxTopBorderTextLen);
this.topBorder = "\x01n" + pCfgObj.colors.listBoxBorder + UPPER_LEFT_SINGLE;
if (addTopTCharsAroundText)
this.topBorder += RIGHT_T_SINGLE;
this.topBorder += "\x01n" + pCfgObj.colors.listBoxBorderText
+ pTopBorderText + "\x01n" + pCfgObj.colors.listBoxBorder;
if (addTopTCharsAroundText)
this.topBorder += LEFT_T_SINGLE;
const topBorderTextLen = strip_ctrl(pTopBorderText).length;
var numHorizBorderChars = innerBorderWidth - topBorderTextLen - 20;
if (addTopTCharsAroundText)
numHorizBorderChars -= 2;
for (var i = 0; i <= numHorizBorderChars; ++i)
this.topBorder += HORIZONTAL_SINGLE;
this.topBorder += RIGHT_T_SINGLE + "\x01n" + pCfgObj.colors.listBoxBorderText
+ "Page 1 of 1" + "\x01n" + pCfgObj.colors.listBoxBorder + LEFT_T_SINGLE
+ UPPER_RIGHT_SINGLE;
// Bottom border string
this.btmBorderNavText = "\x01n\x01h\x01c" + UP_ARROW + "\x01b, \x01c" + DOWN_ARROW + "\x01b, \x01cN\x01y)\x01bext, \x01cP\x01y)\x01brev, "
+ "\x01cF\x01y)\x01birst, \x01cL\x01y)\x01bast, \x01cHOME\x01b, \x01cEND\x01b, \x01cEnter\x01y=\x01bSelect, "
+ "\x01cESC\x01n\x01c/\x01h\x01cQ\x01y=\x01bEnd";
this.bottomBorder = "\x01n" + pCfgObj.colors.listBoxBorder + LOWER_LEFT_SINGLE
+ RIGHT_T_SINGLE + this.btmBorderNavText + "\x01n" + pCfgObj.colors.listBoxBorder
+ LEFT_T_SINGLE;
var numCharsRemaining = this.dimensions.width - strip_ctrl(this.btmBorderNavText).length - 6;
for (var i = 0; i < numCharsRemaining; ++i)
this.bottomBorder += HORIZONTAL_SINGLE;
this.bottomBorder += LOWER_RIGHT_SINGLE;
// Item format strings
this.listIemFormatStr = "\x01n" + pCfgObj.colors.listBoxItemText + "%-"
+ +(this.dimensions.width-2) + "s";
this.listIemHighlightFormatStr = "\x01n" + pCfgObj.colors.listBoxItemHighlight + "%-"
+ +(this.dimensions.width-2) + "s";
// Key functionality override function pointers
this.enterKeyOverrideFn = null;
// inputLoopeExitKeys is an object containing additional keypresses that will
// exit the input loop.
this.inputLoopExitKeys = {};
// For drawing the menu
this.pageNum = 0;
this.numPages = 0;
this.numItemsPerPage = 0;
this.maxItemWidth = 0;
this.pageNumTxtStartX = 0;
// Input loop quit override (to be used in overridden enter function if needed to quit the input loop there
this.continueInputLoopOverride = true;
// Object functions
this.addTextItem = ChoiceScrollbox_AddTextItem; // Returns the index of the item
this.getTextItem = ChoiceScrollbox_GetTextIem;
this.replaceTextItem = ChoiceScrollbox_ReplaceTextItem;
this.delTextItem = ChoiceScrollbox_DelTextItem;
this.chgCharInTextItem = ChoiceScrollbox_ChgCharInTextItem;
this.getChosenTextItemIndex = ChoiceScrollbox_GetChosenTextItemIndex;
this.setItemArray = ChoiceScrollbox_SetItemArray; // Sets the item array; returns whether or not it was set.
this.clearItems = ChoiceScrollbox_ClearItems; // Empties the array of items
this.setEnterKeyOverrideFn = ChoiceScrollbox_SetEnterKeyOverrideFn;
this.clearEnterKeyOverrideFn = ChoiceScrollbox_ClearEnterKeyOverrideFn;
this.addInputLoopExitKey = ChoiceScrollbox_AddInputLoopExitKey;
this.setBottomBorderText = ChoiceScrollbox_SetBottomBorderText;
this.drawBorder = ChoiceScrollbox_DrawBorder;
this.drawInnerMenu = ChoiceScrollbox_DrawInnerMenu;
this.refreshOnScreen = ChoiceScrollbox_RefreshOnScreen;
this.refreshItemCharOnScreen = ChoiceScrollbox_RefreshItemCharOnScreen;
// Does the input loop. Returns an object with the following properties:
// itemWasSelected: Boolean - Whether or not an item was selected
// selectedIndex: The index of the selected item
// selectedItem: The text of the selected item
// lastKeypress: The last key pressed by the user
this.doInputLoop = ChoiceScrollbox_DoInputLoop;
}
function ChoiceScrollbox_AddTextItem(pTextLine, pStripCtrl)
{
var stripCtrl = true;
if (typeof(pStripCtrl) == "boolean")
stripCtrl = pStripCtrl;
if (stripCtrl)
this.txtItemList.push(strip_ctrl(pTextLine));
else
this.txtItemList.push(pTextLine);
// Return the index of the added item
return this.txtItemList.length-1;
}
function ChoiceScrollbox_GetTextIem(pItemIndex)
{
if (typeof(pItemIndex) != "number")
return "";
if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length))
return "";
return this.txtItemList[pItemIndex];
}
function ChoiceScrollbox_ReplaceTextItem(pItemIndexOrStr, pNewItem)
{
if (typeof(pNewItem) != "string")
return false;
// Find the item index
var itemIndex = -1;
if (typeof(pItemIndexOrStr) == "number")
{
if ((pItemIndexOrStr < 0) || (pItemIndexOrStr >= this.txtItemList.length))
return false;
else
itemIndex = pItemIndexOrStr;
}
else if (typeof(pItemIndexOrStr) == "string")
{
itemIndex = -1;
for (var i = 0; (i < this.txtItemList.length) && (itemIndex == -1); ++i)
{
if (this.txtItemList[i] == pItemIndexOrStr)
itemIndex = i;
}
}
else
return false;
// Replace the item
var replacedIt = false;
if ((itemIndex > -1) && (itemIndex < this.txtItemList.length))
{
this.txtItemList[itemIndex] = pNewItem;
replacedIt = true;
}
return replacedIt;
}
function ChoiceScrollbox_DelTextItem(pItemIndexOrStr)
{
// Find the item index
var itemIndex = -1;
if (typeof(pItemIndexOrStr) == "number")
{
if ((pItemIndexOrStr < 0) || (pItemIndexOrStr >= this.txtItemList.length))
return false;
else
itemIndex = pItemIndexOrStr;
}
else if (typeof(pItemIndexOrStr) == "string")
{
itemIndex = -1;
for (var i = 0; (i < this.txtItemList.length) && (itemIndex == -1); ++i)
{
if (this.txtItemList[i] == pItemIndexOrStr)
itemIndex = i;
}
}
else
return false;
// Remove the item
var removedIt = false;
if ((itemIndex > -1) && (itemIndex < this.txtItemList.length))
{
this.txtItemList = this.txtItemList.splice(itemIndex, 1);
removedIt = true;
}
return removedIt;
}
function ChoiceScrollbox_ChgCharInTextItem(pItemIndexOrStr, pStrIndex, pNewText)
{
// Find the item index
var itemIndex = -1;
if (typeof(pItemIndexOrStr) == "number")
{
if ((pItemIndexOrStr < 0) || (pItemIndexOrStr >= this.txtItemList.length))
return false;
else
itemIndex = pItemIndexOrStr;
}
else if (typeof(pItemIndexOrStr) == "string")
{
itemIndex = -1;
for (var i = 0; (i < this.txtItemList.length) && (itemIndex == -1); ++i)
{
if (this.txtItemList[i] == pItemIndexOrStr)
itemIndex = i;
}
}
else
return false;
// Change the character in the item
var changedIt = false;
if ((itemIndex > -1) && (itemIndex < this.txtItemList.length))
{
this.txtItemList[itemIndex] = chgCharInStr(this.txtItemList[itemIndex], pStrIndex, pNewText);
changedIt = true;
}
return changedIt;
}
function ChoiceScrollbox_GetChosenTextItemIndex()
{
return this.chosenTextItemIndex;
}
function ChoiceScrollbox_SetItemArray(pArray, pStripCtrl)
{
var safeToSet = false;
if (Object.prototype.toString.call(pArray) === "[object Array]")
{
if (pArray.length > 0)
safeToSet = (typeof(pArray[0]) == "string");
else
safeToSet = true; // It's safe to set an empty array
}
if (safeToSet)
{
delete this.txtItemList;
this.txtItemList = pArray;
var stripCtrl = true;
if (typeof(pStripCtrl) == "boolean")
stripCtrl = pStripCtrl;
if (stripCtrl)
{
// Remove attribute/color characters from the text lines in the array
for (var i = 0; i < this.txtItemList.length; ++i)
this.txtItemList[i] = strip_ctrl(this.txtItemList[i]);
}
}
return safeToSet;
}
function ChoiceScrollbox_ClearItems()
{
this.txtItemList.length = 0;
}
function ChoiceScrollbox_SetEnterKeyOverrideFn(pOverrideFn)
{
if (Object.prototype.toString.call(pOverrideFn) == "[object Function]")
this.enterKeyOverrideFn = pOverrideFn;
}
function ChoiceScrollbox_ClearEnterKeyOverrideFn()
{
this.enterKeyOverrideFn = null;
}
function ChoiceScrollbox_AddInputLoopExitKey(pKeypress)
{
this.inputLoopExitKeys[pKeypress] = true;
}
function ChoiceScrollbox_SetBottomBorderText(pText, pAddTChars, pAutoStripIfTooLong)
{
if (typeof(pText) != "string")
return;
const innerWidth = (pAddTChars ? this.dimensions.width-4 : this.dimensions.width-2);
if (pAutoStripIfTooLong)
{
if (strip_ctrl(pText).length > innerWidth)
pText = pText.substr(0, innerWidth);
}
// Re-build the bottom border string based on the new text
this.bottomBorder = "\x01n" + this.programCfgObj.colors.listBoxBorder + LOWER_LEFT_SINGLE;
if (pAddTChars)
this.bottomBorder += RIGHT_T_SINGLE;
if (pText.indexOf("\x01n") != 0)
this.bottomBorder += "\x01n";
this.bottomBorder += pText + "\x01n" + this.programCfgObj.colors.listBoxBorder;
if (pAddTChars)
this.bottomBorder += LEFT_T_SINGLE;
var numCharsRemaining = this.dimensions.width - strip_ctrl(this.bottomBorder).length - 3;
for (var i = 0; i < numCharsRemaining; ++i)
this.bottomBorder += HORIZONTAL_SINGLE;
this.bottomBorder += LOWER_RIGHT_SINGLE;
}
function ChoiceScrollbox_DrawBorder()
{
console.gotoxy(this.dimensions.topLeftX, this.dimensions.topLeftY);
console.print(this.topBorder);
// Draw the side border characters
var screenRow = this.dimensions.topLeftY + 1;
for (var screenRow = this.dimensions.topLeftY+1; screenRow <= this.dimensions.bottomRightY-1; ++screenRow)
{
console.gotoxy(this.dimensions.topLeftX, screenRow);
console.print(VERTICAL_SINGLE);
console.gotoxy(this.dimensions.bottomRightX, screenRow);
console.print(VERTICAL_SINGLE);
}
// Draw the bottom border
console.gotoxy(this.dimensions.topLeftX, this.dimensions.bottomRightY);
console.print(this.bottomBorder);
}
function ChoiceScrollbox_DrawInnerMenu(pSelectedIndex)
{
var selectedIndex = (typeof(pSelectedIndex) == "number" ? pSelectedIndex : -1);
var startArrIndex = this.pageNum * this.numItemsPerPage;
var endArrIndex = startArrIndex + this.numItemsPerPage;
if (endArrIndex > this.txtItemList.length)
endArrIndex = this.txtItemList.length;
var selectedItemRow = this.dimensions.topLeftY+1;
var screenY = this.dimensions.topLeftY + 1;
for (var i = startArrIndex; i < endArrIndex; ++i)
{
console.gotoxy(this.dimensions.topLeftX+1, screenY);
if (i == selectedIndex)
{
printf(this.listIemHighlightFormatStr, this.txtItemList[i].substr(0, this.maxItemWidth));
selectedItemRow = screenY;
}
else
printf(this.listIemFormatStr, this.txtItemList[i].substr(0, this.maxItemWidth));
++screenY;
}
// If the current screen row is below the bottom row inside the box,
// continue and write blank lines to the bottom of the inside of the box
// to blank out any text that might still be there.
while (screenY < this.dimensions.topLeftY+this.dimensions.height-1)
{
console.gotoxy(this.dimensions.topLeftX+1, screenY);
printf(this.listIemFormatStr, "");
++screenY;
}
// Update the page number in the top border of the box.
console.gotoxy(this.pageNumTxtStartX, this.dimensions.topLeftY);
printf("\x01n" + this.programCfgObj.colors.listBoxBorderText + "Page %4d of %4d", this.pageNum+1, this.numPages);
return selectedItemRow;
}
function ChoiceScrollbox_RefreshOnScreen(pSelectedIndex)
{
this.drawBorder();
this.drawInnerMenu(pSelectedIndex);
}
function ChoiceScrollbox_RefreshItemCharOnScreen(pItemIndex, pCharIndex)
{
if ((typeof(pItemIndex) != "number") || (typeof(pCharIndex) != "number"))
return;
if ((pItemIndex < 0) || (pItemIndex >= this.txtItemList.length) ||
(pItemIndex < this.topItemIndex) || (pItemIndex > this.bottomItemIndex))
{
return;
}
if ((pCharIndex < 0) || (pCharIndex >= this.txtItemList[pItemIndex].length))
return;
// Save the current cursor position so that we can restore it later
const originalCurpos = console.getxy();
// Go to the character's position on the screen and set the highlight or
// normal color, depending on whether the item is the currently selected item,
// then print the character on the screen.
const charScreenX = this.dimensions.topLeftX + 1 + pCharIndex;
const itemScreenY = this.dimensions.topLeftY + 1 + (pItemIndex - this.topItemIndex);
console.gotoxy(charScreenX, itemScreenY);
if (pItemIndex == this.chosenTextItemIndex)
console.print(this.programCfgObj.colors.listBoxItemHighlight);
else
console.print(this.programCfgObj.colors.listBoxItemText);
console.print(this.txtItemList[pItemIndex].charAt(pCharIndex));
// Move the cursor back to where it was originally
console.gotoxy(originalCurpos);
}
function ChoiceScrollbox_DoInputLoop(pDrawBorder)
{
var retObj = {
itemWasSelected: false,
selectedIndex: -1,
selectedItem: "",
lastKeypress: ""
};
// Don't do anything if the item list doesn't contain any items
if (this.txtItemList.length == 0)
return retObj;
//////////////////////////////////
// Locally-defined functions
// This function returns the index of the bottommost item that
// can be displayed in the box.
//
// Parameters:
// pArray: The array containing the items
// pTopindex: The index of the topmost item displayed in the box
// pNumItemsPerPage: The number of items per page
function getBottommostItemIndex(pArray, pTopIndex, pNumItemsPerPage)
{
var bottomIndex = pTopIndex + pNumItemsPerPage - 1;
// If bottomIndex is beyond the last index, then adjust it.
if (bottomIndex >= pArray.length)
bottomIndex = pArray.length - 1;
return bottomIndex;
}
//////////////////////////////////
// Code
// Variables for keeping track of the item list
this.numItemsPerPage = this.dimensions.height - 2;
this.topItemIndex = 0; // The index of the message group at the top of the list
// Figure out the index of the last message group to appear on the screen.
this.bottomItemIndex = getBottommostItemIndex(this.txtItemList, this.topItemIndex, this.numItemsPerPage);
this.numPages = Math.ceil(this.txtItemList.length / this.numItemsPerPage);
const topIndexForLastPage = (this.numItemsPerPage * this.numPages) - this.numItemsPerPage;
if (pDrawBorder)
this.drawBorder();
// User input loop
// For the horizontal location of the page number text for the box border:
// Based on the fact that there can be up to 9999 text replacements and 10
// per page, there will be up to 1000 pages of replacements. To write the
// text, we'll want to be 20 characters to the left of the end of the border
// of the box.
this.pageNumTxtStartX = this.dimensions.topLeftX + this.dimensions.width - 19;
this.maxItemWidth = this.dimensions.width - 2;
this.pageNum = 0;
var startArrIndex = 0;
this.chosenTextItemIndex = retObj.selectedIndex = 0;
var endArrIndex = 0; // One past the last array item
var curpos = { // For keeping track of the current cursor position
x: 0,
y: 0
};
var refreshList = true; // For screen redraw optimizations
this.continueInputLoopOverride = true;
var continueOn = true;
while (continueOn && this.continueInputLoopOverride)
{
if (refreshList)
{
this.bottomItemIndex = getBottommostItemIndex(this.txtItemList, this.topItemIndex, this.numItemsPerPage);
// Write the list of items for the current page. Also, drawInnerMenu()
// will return the selected item row.
var selectedItemRow = this.drawInnerMenu(retObj.selectedIndex);
// Just for sane appearance: Move the cursor to the first character of
// the currently-selected row and set the appropriate color.
curpos.x = this.dimensions.topLeftX+1;
curpos.y = selectedItemRow;
console.gotoxy(curpos.x, curpos.y);
console.print(this.programCfgObj.colors.listBoxItemHighlight);
refreshList = false;
}
// Get a key from the user (upper-case) and take action based upon it.
retObj.lastKeypress = getKeyWithESCChars(K_UPPER|K_NOCRLF|K_NOSPIN, this.programCfgObj);
switch (retObj.lastKeypress)
{
case 'N': // Next page
case KEY_PAGE_DOWN:
refreshList = (this.pageNum < this.numPages-1);
if (refreshList)
{
++this.pageNum;
this.topItemIndex += this.numItemsPerPage;
this.chosenTextItemIndex = retObj.selectedIndex = this.topItemIndex;
// Note: this.bottomItemIndex is refreshed at the top of the loop
}
break;
case 'P': // Previous page
case KEY_PAGE_UP:
refreshList = (this.pageNum > 0);
if (refreshList)
{
--this.pageNum;
this.topItemIndex -= this.numItemsPerPage;
this.chosenTextItemIndex = retObj.selectedIndex = this.topItemIndex;
// Note: this.bottomItemIndex is refreshed at the top of the loop
}
break;
case 'F': // First page
refreshList = (this.pageNum > 0);
if (refreshList)
{
this.pageNum = 0;
this.topItemIndex = 0;
this.chosenTextItemIndex = retObj.selectedIndex = this.topItemIndex;
// Note: this.bottomItemIndex is refreshed at the top of the loop
}
break;
case 'L': // Last page
refreshList = (this.pageNum < this.numPages-1);
if (refreshList)
{
this.pageNum = this.numPages-1;
this.topItemIndex = topIndexForLastPage;
this.chosenTextItemIndex = retObj.selectedIndex = this.topItemIndex;
// Note: this.bottomItemIndex is refreshed at the top of the loop
}
break;
case KEY_UP:
// Move the cursor up one item
if (retObj.selectedIndex > 0)
{
// If the previous item index is on the previous page, then we'll
// want to display the previous page.
var previousItemIndex = retObj.selectedIndex - 1;
if (previousItemIndex < this.topItemIndex)
{
--this.pageNum;
this.topItemIndex -= this.numItemsPerPage;
// Note: this.bottomItemIndex is refreshed at the top of the loop
refreshList = true;
}
else
{
// Display the current line un-highlighted
console.gotoxy(this.dimensions.topLeftX+1, curpos.y);
printf(this.listIemFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
// Display the previous line highlighted
curpos.x = this.dimensions.topLeftX+1;
--curpos.y;
console.gotoxy(curpos);
printf(this.listIemHighlightFormatStr, this.txtItemList[previousItemIndex].substr(0, this.maxItemWidth));
console.gotoxy(curpos); // Move the cursor into place where it should be
refreshList = false;
}
this.chosenTextItemIndex = retObj.selectedIndex = previousItemIndex;
}
break;
case KEY_DOWN:
// Move the cursor down one item
if (retObj.selectedIndex < this.txtItemList.length - 1)
{
// If the next item index is on the next page, then we'll want to
// display the next page.
var nextItemIndex = retObj.selectedIndex + 1;
if (nextItemIndex > this.bottomItemIndex)
{
++this.pageNum;
this.topItemIndex += this.numItemsPerPage;
// Note: this.bottomItemIndex is refreshed at the top of the loop
refreshList = true;
}
else
{
// Display the current line un-highlighted
console.gotoxy(this.dimensions.topLeftX+1, curpos.y);
printf(this.listIemFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
// Display the previous line highlighted
curpos.x = this.dimensions.topLeftX+1;
++curpos.y;
console.gotoxy(curpos);
printf(this.listIemHighlightFormatStr, this.txtItemList[nextItemIndex].substr(0, this.maxItemWidth));
console.gotoxy(curpos); // Move the cursor into place where it should be
refreshList = false;
}
this.chosenTextItemIndex = retObj.selectedIndex = nextItemIndex;
}
break;
case KEY_HOME: // Go to the first row in the box
if (retObj.selectedIndex > this.topItemIndex)
{
// Display the current line un-highlighted
console.gotoxy(this.dimensions.topLeftX+1, curpos.y);
printf(this.listIemFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
// Select the top item, and display it highlighted.
this.chosenTextItemIndex = retObj.selectedIndex = this.topItemIndex;
curpos.x = this.dimensions.topLeftX+1;
curpos.y = this.dimensions.topLeftY+1;
console.gotoxy(curpos);
printf(this.listIemHighlightFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
console.gotoxy(curpos); // Move the cursor into place where it should be
refreshList = false;
}
break;
case KEY_END: // Go to the last row in the box
if (retObj.selectedIndex < this.bottomItemIndex)
{
// Display the current line un-highlighted
console.gotoxy(this.dimensions.topLeftX+1, curpos.y);
printf(this.listIemFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
// Select the bottommost item, and display it highlighted.
this.chosenTextItemIndex = retObj.selectedIndex = this.bottomItemIndex;
curpos.x = this.dimensions.topLeftX+1;
curpos.y = this.dimensions.bottomRightY-1;
console.gotoxy(curpos);
printf(this.listIemHighlightFormatStr, this.txtItemList[retObj.selectedIndex].substr(0, this.maxItemWidth));
console.gotoxy(curpos); // Move the cursor into place where it should be
refreshList = false;
}
break;
case KEY_ENTER:
// If the enter key override function is set, then call it and pass
// this object into it. Otherwise, just select the item and quit.
if (this.enterKeyOverrideFn !== null)
this.enterKeyOverrideFn(this);
else
{
retObj.itemWasSelected = true;
// Note: retObj.selectedIndex is already set.
retObj.selectedItem = this.txtItemList[retObj.selectedIndex];
refreshList = false;
continueOn = false;
}
break;
case KEY_ESC: // Quit
case CTRL_A: // Quit
case 'Q': // Quit
this.chosenTextItemIndex = retObj.selectedIndex = -1;
refreshList = false;
continueOn = false;
break;
default:
// If the keypress is an additional key to exit the input loop, then
// do so.
if (this.inputLoopExitKeys.hasOwnProperty(retObj.lastKeypress))
{
this.chosenTextItemIndex = retObj.selectedIndex = -1;
refreshList = false;
continueOn = false;
}
else
{
// Unrecognized command. Don't refresh the list of the screen.
refreshList = false;
}
break;
}
}
this.continueInputLoopOverride = true; // Reset
console.print("\x01n"); // To prevent outputting highlight colors, etc..
return retObj;
}
///////////////////////////////////////////////////////////////////////////////////
// Writes a default twitlist for the user if it doesn't exist
function writeDefaultUserTwitListIfNotExist()
{
if (file_exists(gUserTwitListFilename))
return;
var outFile = new File(gUserTwitListFilename);
if (outFile.open("w"))
{
outFile.writeln("; This is a personal twitlist for Digital Distortion Message Reader to block");
outFile.writeln("; messages from (and to) certain usernames. The intention is that if you are");
outFile.writeln("; being harassed by a specific person, or you simply do not wish to see their");
outFile.writeln("; messages, you can filter that person by adding their name (or email address)");
outFile.writeln("; to this file.");
outFile.close();
}
}
// Returns whether 2 arrays have the same values
function arraysHaveSameValues(pArray1, pArray2)
{
if (pArray1 == null && pArray2 == null)
return true;
else if (pArray1 != null && pArray2 == null)