9 extern int __ecereVMethodID_class_OnFree;
10 extern int __ecereVMethodID_class_OnGetString;
11 extern int __ecereVMethodID_class_OnGetDataFromString;
16 // #define _DEBUG_LINE
19 #define FULL_STRING_SEARCH
21 #define UTF8_IS_FIRST(x) (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
22 #define UTF8_NUM_BYTES(x) (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
24 define newEntryStringDebug = $"New|id=";
25 define newEntryString = $"New";
27 public char ToASCII(unichar ch)
32 if(ch == 'À' || ch == 'Á' || ch == 'Â' || ch == 'Ã' || ch == 'Ä' || ch == 'Å')
36 else if(ch == 'È' || ch == 'É' || ch == 'Ê' || ch == 'Ë')
38 else if(ch == 'Ì' || ch == 'Í' || ch == 'Î' || ch == 'Ï')
42 else if(ch == 'Ò' || ch == 'Ó' || ch == 'Ô' || ch == 'Õ' || ch == 'Ö')
44 else if(ch == 'Ù' || ch == 'Ú' || ch == 'Û' || ch == 'Ü')
46 else if(ch == 'à' || ch == 'á' || ch == 'â' || ch == 'ã' || ch == 'ä' || ch == 'å')
50 else if(ch == 'è' || ch == 'é' || ch == 'ê' || ch == 'ë')
52 else if(ch == 'ì' || ch == 'í' || ch == 'î' || ch == 'ï')
56 else if(ch == 'ò' || ch == 'ó' || ch == 'ô' || ch == 'õ' || ch == 'ö')
58 else if(ch == 'ù' || ch == 'ú' || ch == 'û' || ch == 'ü')
66 public char * ConvertToASCII(char * string, char * newString, int * len, bool lowerCase)
73 for(c = 0; (unich = UTF8GetChar(string + c, &nb)); c += nb)
75 char ch = ToASCII(unich);
77 newString[d++] = lowerCase ? (char)tolower(ch) : (char)ch;
85 public class NoCaseAccent : SQLCustomFunction
87 Array<char> array { minAllocSize = 1024 };
89 String function(String text)
91 int len = text ? strlen(text) : 0;
93 ConvertToASCII(text ? text : "", array.array, &len, true);
98 public class MemberStringSample
103 default extern int __ecereVMethodID_class_OnUnserialize;
105 public class GetMemberString<class NT:void * = MemberStringSample, name = NT::name> : NoCaseAccent
108 String function(NT pn)
110 return NoCaseAccent::function((pn && pn.name) ? pn.name : null);
114 // The old way is still possible...
115 SerialBuffer buffer { };
116 String function(void * ptr)
121 buffer.count = MAXINT;
123 ((void (*)(void *, void *, void *))(void *)class(NT)._vTbl[__ecereVMethodID_class_OnUnserialize])(class(NT), &pn, buffer);
124 result = NoCaseAccent::function((pn && pn.name) ? pn.name : null);
126 buffer.buffer = null;
128 // TOFIX: If we name GetName's type 'T', the following name confuses with Array's 'T'
129 //ConvertToASCII(s ? s : "", array.array, &len, true);
135 // Rename TableEditor to TableControl and move to eda/src/gui/controls
136 public class TableEditor : public Window
143 DebugLn("TableEditor::table|set");
146 get { return table; }
153 DebugLn("TableEditor::index|set");
155 filterRow.tbl = index;
157 get { return index; }
162 DebugLn("TableEditor::OnPostCreate");
170 InitFieldsBoxes(); // IMPORTANT: table must be set *AFTER* all related FieldEditors have been initialized
172 Field fldId = idField, fldName = stringField, fldActive = null;
173 FieldIndex indexedFields[1];
174 if(!idField) fldId = table.FindField(defaultIdField);
175 if(!fldName) fldName = table.FindField(defaultNameField);
176 if(!fldActive) fldActive = table.FindField(defaultActiveField);
177 indexedFields[0] = { fldId };
178 table.Index(1, indexedFields);
186 if(!listEnumerationTimer.hasCompleted)
188 if(list && !list.currentRow)
189 list.SelectRow(list.firstRow); // should the tableeditor select method be used here?
194 bool OnClose(bool parentClosing)
196 bool result = NotifyClosing();
205 property ListBox list
209 DebugLn("TableEditor::list|set");
214 property Array<ListField> listFields
218 DebugLn("TableEditor::listFields|set");
223 property int listSortOrder
225 set { listSortOrder = value; }
226 get { return listSortOrder; }
228 property DataField listSortField
230 set { listSortField = value; }
231 get { return listSortField; }
233 property bool disabledFullListing
235 set { disabledFullListing = value; }
236 get { return disabledFullListing; }
239 property Array<StringSearchField> searchFields
243 StringSearchTable searchTable { table, idField, value };
244 DebugLn("TableEditor::searchFields|set");
245 // The searchTables property will incref...
246 property::searchTables = { [ searchTable ] };
250 property Array<StringSearchTable> searchTables
254 // This API is not very clear on ownership of search tables array/search table/field...
255 // Right now both the array and tables themselves are incref'ed here
257 for(t : value) { incref t; }
258 DebugLn("TableEditor::searchTables|set");
259 if(searchTables) searchTables.Free();
261 searchTables = value;
265 property Array<SQLiteSearchTable> sqliteSearchTables
270 for(t : value) { incref t; }
271 DebugLn("TableEditor::searchTables|set");
272 if(sqliteSearchTables) sqliteSearchTables.Free();
273 delete sqliteSearchTables;
274 sqliteSearchTables = value;
278 property String searchString
282 bool modified = modifiedDocument;
283 DebugLn("TableEditor::searchString|set");
284 switch(modified ? OnLeavingModifiedDocument() : no)
294 if(value && value[0])
295 searchString = CopyString(value);
301 property Array<LookupEditor> dynamicLookupEditors
305 DebugLn("TableEditor::dynamicLookupEditors|set");
306 dynamicLookupEditors = value;
311 property Id selectedId { get { return selectedId; } }
313 property Field idField
315 set { idField = value; }
316 get { return idField; }
318 property Field stringField
320 set { stringField = value; }
321 get { return stringField; }
323 property Field indexFilterField
325 set { indexFilterField = value; }
326 get { return indexFilterField; }
329 property bool readOnly
331 set { readOnly = value; }
332 get { return readOnly; }
335 virtual void OnInitialize();
336 virtual void OnLoad();
337 virtual void OnStateChanged();
339 void NotifyModifiedDocument()
341 DebugLn("TableEditor::NotifyModifiedDocument");
342 if(!internalModifications)
346 //virtual bool Window::NotifyNew(AltListSection listSection, Row r);
347 //virtual void Window::NotifyInitFields(AltEditSection editSection);
349 virtual DialogResult OnLeavingModifiedDocument()
351 DebugLn("TableEditor::OnLeavingModifiedDocument");
352 return MessageBox { master = this, type = yesNoCancel, text = text && text[0] ? text : $"Table Editor",
353 contents = $"You have modified this entry. Would you like to save it before proceeding?"
357 virtual bool OnRemovalRequest()
359 DebugLn("TableEditor::OnRemovalRequest");
360 return MessageBox { master = this, type = yesNo, text = text && text[0] ? text : $"Table Editor",
361 contents = $"You are about to permanently remove an entry.\n"
362 "Do you wish to continue?"
366 //virtual void Window::NotifyDeleting(ListSection listSection);
367 //virtual void Window::NotifyDeleted(ListSection listSection);
372 DebugLn("TableEditor::NotifyClosing");
375 switch(OnLeavingModifiedDocument())
389 StopWordListPrepTimer();
390 StopListEnumerationTimer();
395 //void List() // this gets called out of nowhere by some constructor thing...
398 DebugLn("TableEditor::Enumerate");
401 StopListEnumerationTimer();
405 if(list || OnList != TableEditor::OnList)
408 Array<Id> matches = null;
409 listEnumerationTimer.sqliteSearch = false;
410 if(searchTables && searchTables.count)
411 matches = SearchWordList();
412 //else if(sqliteSearchTables && sqliteSearchTables.count)
413 //matches = SearchSQLite();
414 else if(searchString && searchString[0] &&
415 sqliteSearchTables && sqliteSearchTables.count &&
416 sqliteSearchTables[0].table && sqliteSearchTables[0].idField &&
417 sqliteSearchTables[0].searchFields && sqliteSearchTables[0].searchFields.count &&
418 sqliteSearchTables[0].searchFields[0].field)
419 listEnumerationTimer.sqliteSearch = true;
420 if(matches && matches.count)
421 PrintLn("results count: ", matches.count);
426 modifiedDocument = false; // setting this here is not really logical, enumeration and modified have nothing to do with eachother
429 virtual void OnList(Row r, Array<Id> matches)
431 DebugLn("TableEditor::OnList");
432 if(!listEnumerationTimer.started)
434 listEnumerationTimer.hasCompleted = false;
435 listEnumerationTimer.matchesIndex = 0;
436 listEnumerationTimer.tablesIndex = 0;
437 if(!listEnumerationTimer.sqliteSearch)
438 listEnumerationTimer.row = Row { r.tbl };
441 listEnumerationTimer.matches = { };
442 // TOFIX: This (void *) cast here should NOT be required... Fix this Container warning:
443 // warning: incompatible expression matches (ecere::com::Array<eda::Id>); expected ecere::com::Container<T>
444 listEnumerationTimer.matches.Copy((void *)matches);
447 listEnumerationTimer.matches = null;
448 listEnumerationTimer.Start();
451 DebugLn("TableEditor::OnList -- timer state error");
454 virtual void OnCreateDynamicLookupEditors()
456 DebugLn("TableEditor::OnCreateLookupEditors");
457 if(dynamicLookupEditors && dynamicLookupEditors.count)
459 for(f : dynamicLookupEditors)
461 if(f.editorClass && f.parentWindow && f.lookupFindField)
463 Row row { f.lookupFindIndex ? f.lookupFindIndex : f.lookupFindField.table };
464 // todo: make this work for all types
466 editRow.GetData(f.lookupValueField, id);
467 // TODO: add alternative class instance for creation when no rows are found via lookup
468 for(row.Find(f.lookupFindField, middle, nil, id); !row.nil; row.Next())
470 // todo: make this work for all types, although this is meant to be an id field
472 TableEditor editor = eInstance_New(f.editorClass);
474 editor.parent = f.parentWindow;
475 editor.master = this;
476 dynamicLookupTableEditors.Add(editor);
478 row.GetData(f.lookupIdField, id);
487 property TableEditor masterEditor
491 if(value != masterEditor)
494 masterEditor.RemoveTableEditor(this);
495 masterEditor = value;
497 value.AddTableEditor(this);
500 get { return masterEditor; }
505 if(eClass_IsDerived(parent._class, class(TableEditor)))
506 property::masterEditor = (TableEditor)parent;
511 if(eClass_IsDerived(master._class, class(TableEditor)))
512 property::masterEditor = (TableEditor)master;
515 watch(modifiedDocument)
517 NotifyModifiedDocument();
522 DebugLn("TableEditor::CreateRow");
523 //list.NotifySelect(this, list, null, 0);
524 if(table && editRow && editRow.tbl && !modifiedDocument)
526 uint id; // = table.rowsCount + 1; // this is bad with deleted rows, won't work, how to have unique id?
527 // I think the 3 following comment lines apply to the old sqlite driver before many fix we done for wsms
528 Row r = editRow;// { table }; // the multipurpose row is buggy with sqlite driver, you can't use the same row to do Row::Last(), Row::Next(), Row::Find(), etc...
529 //Row r { editRow.tbl }; // for example, Row::Last() here is not using the proper sqlite statement and fails to
530 // return false when no rows are present in a table
534 /*uint count = editRow.tbl.GetRowsCount();
537 // r.Last() is returning true even if there are not rows in this table (SQLite)
538 if(count && !(r.Last() || r.Last()))
539 DebugLn("PROBLEM");*/
540 if(r.Last()) // this will reuse ids in cases where the item(s) with the last id have been deleted
542 r.GetData(idField, id);
553 // Patch for SQLite driver which auto-increments IDs
555 if(r.GetData(idField, curId))
558 r.SetData(idField, id);
561 r.SetData(fldActive, active);*/
565 newText = PrintString("[", newEntryStringDebug, id, "]");
567 newText = PrintString("[", newEntryString, "]");
570 //if(NotifyNew(master, this, r))
571 if(listFields && idField)
575 if(lf.dataField && lf.field)
577 if(lf.field.type == class(String))
578 r.SetData(lf.field, newText);
579 else // this whole block is new?
581 if(lf.field.type._vTbl[__ecereVMethodID_class_OnGetDataFromString])
583 Class dataType = lf.field.type;
584 int64 dataHolder = 0;
585 void * data = &dataHolder;
587 if(dataType && dataType.type == structClass)
589 dataHolder = (int64)new0 byte[dataType.structSize];
590 data = (void *)dataHolder;
592 /*else if(dataType && (dataType.type == noHeadClass || dataType.type == normalClass))
594 if(eClass_IsDerived(dataType, class(char*)))
595 dataHolder = (int64)CopyString("");
597 dataHolder = (int64)eInstance_New(dataType);
598 data = (void *)&dataHolder;
606 ((bool (*)(void *, void *, const char *))(void *)dataType._vTbl[__ecereVMethodID_class_OnGetDataFromString])(dataType, data, newText);
609 /*((void (*)(void *, void *))(void *)dataType._vTbl[__ecereVMethodID_class_OnFree])(dataType, dataHolder);
610 if(dataType.type == structClass)
612 void * dataPtr = (void *)dataHolder;
624 // have a better technique than Row::Next(); Row::Find(...); to make sure Row::GetData() will work right after a Row::SetData()?
625 // it seems we're missing Row::Update()
630 // next line is a patch for SQLite not returning data from GetData right after a SetData
631 if(idField && r.Find(idField, middle, nil, id))
632 SetListRowFields(r, row, false);
635 else if(idField && stringField)
637 r.SetData(stringField, newText);
640 row = list.AddString(newText);
650 list.Sort(listSortField, listSortOrder);
651 if(row) SelectListRow(row);
659 DebugLn("TableEditor::Remove");
660 if(editRow.sysID) //list && list.currentRow)
662 if(OnRemovalRequest())
666 list.DeleteRow(list.currentRow);
668 //NotifyDeleted(master, this);
670 SelectListRow(list.currentRow);
678 DebugLn("TableEditor::Load");
684 DebugLn("TableEditor::Write");
688 bool ListSelect(DataRow row)
691 DebugLn("TableEditor::ListSelect");
692 if(/*-row && -*/row != lastRow)
698 list.currentRow = lastRow;
700 switch(OnLeavingModifiedDocument())
708 list.currentRow = row;
712 if(list.currentRow == row)
721 DebugLn("TableEditor::Select");
722 // EDA is now set up so that Next()/Prev() will work with sysID = , but not with Find() (As Find() will return a particular set of results)
723 if(idField && editRow && (editRow.sysID = id, !editRow.nil))// && editRow.Find(idField, middle, nil, id))
725 selectedId = editRow.sysID;
737 DebugLn("TableEditor::Filter");
738 if(selectedId && index && indexFilterField)
740 for(filterRow.Find(indexFilterField, middle, nil, id); !filterRow.nil; filterRow.Next())
743 filterRow.GetData(idField, id2);
744 if(id2 == selectedId)
757 bool SelectNext(bool loopAround)
759 bool result = NotifyClosing();
760 bool wasNil = !editRow.sysID;
761 DebugLn("TableEditor::SelectNext");
766 if(!filterRow.Next() && loopAround)
768 //filterRow.First(); // Row::First doesn't behave properly in a filtered table
769 while(filterRow.Previous())
775 if(wasNil && filterRow.sysID == selectedId)
776 filterRow.Next(); // this whole wasNil thing makes no sense to me?
777 editRow.sysID = filterRow.sysID;
784 if(!editRow.Next() && loopAround)
789 selectedId = editRow.sysID;
798 bool SelectPrevious(bool loopAround)
800 bool result = NotifyClosing();
801 bool wasNil = !editRow.sysID;
802 DebugLn("TableEditor::SelectPrevious");
807 if(!filterRow.Previous() && loopAround)
809 //filterRow.Last(); // Row::Last doesn't behave properly in a filtered table
810 while(filterRow.Next())
812 filterRow.Previous();
816 if(wasNil && filterRow.sysID == selectedId)
817 filterRow.Previous(); // this whole wasNil thing makes no sense to me?
818 editRow.sysID = filterRow.sysID;
825 if(!editRow.Previous() && loopAround)
830 selectedId = editRow.sysID;
839 void SelectListRow(DataRow row)
841 // Time startTime = GetTime();
842 DebugLn("TableEditor::SelectListRow");
845 // TOFIX: Id is still 32-bit; Also the warning without this cast seems wrong (It says row.tag is of type eda::Id, while it is int64)
846 selectedId = (Id)row.tag;
849 if(list.currentRow != row)
850 list.currentRow = row;
851 if(idField && editRow.Find(idField, middle, nil, selectedId))
854 //NotifySelectListRow(master, this, selectedId);
858 // Logf("SelectListRow took %f seconds\n", GetTime() - startTime);
866 Field indexFilterField;
869 Array<StringSearchTable> searchTables;
870 Array<SQLiteSearchTable> sqliteSearchTables;
873 Map<Table, Lookup> lookups;
875 Array<LookupEditor> dynamicLookupEditors;
876 Array<FieldBox> fieldsBoxes { };
877 Array<TableEditor> tableEditors { };
878 Array<TableEditor> dynamicLookupTableEditors { };
881 bool internalModifications;
882 TableEditor masterEditor;
890 Array<char> searchCI { };
891 WordEntry letters[26];
892 WordEntry doubleLetters[26][26];
894 Array<ListField> listFields;
896 DataField listSortField;
897 bool disabledFullListing;
899 ListEnumerationTimer listEnumerationTimer
901 userData = this, delay = 0.1f;
904 static Time startTime;
907 Row row = listEnumerationTimer.row;
908 Array<Id> matches = listEnumerationTimer.matches;
909 Time delay = listEnumerationTimer.delay;
910 Time lastTime = GetTime();
911 static int slice = 128;
913 static int wordListPrepRowCount = 0, wordListPrepRowNum = 0, ticks = 0;
916 PrintLn("listing... ");
920 int index = listEnumerationTimer.matchesIndex;
921 if(listFields && idField)
923 for(c=0; c<slice && (next = index++<matches.count); c++)
925 if(row.Find(idField, middle, nil, matches[index]))
928 DataRow dataRow = list.AddRow();
929 row.GetData(idField, id);
931 SetListRowFields(row, dataRow, true);
934 DebugLn($"WordList match cannot be found in database.");
936 listEnumerationTimer.matchesIndex = index;
937 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
939 else if(idField && stringField)
941 for(c=0; c<slice && (next = index++<matches.count); c++)
943 if(row.Find(idField, middle, nil, matches[index]))
947 row.GetData(idField, id);
948 row.GetData(stringField, s);
949 list.AddString(s).tag = id;
953 DebugLn($"WordList match cannot be found in database.");
955 listEnumerationTimer.matchesIndex = index;
956 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
959 else if(listEnumerationTimer.sqliteSearch)
961 static SQLiteSearchTable st = null;
962 Row lookupRow { table };
963 Map<Id, int> uniques = listEnumerationTimer.uniques;
966 if(listEnumerationTimer.tablesIndex < sqliteSearchTables.count)
968 char queryString[4096*4];
970 if(!listEnumerationTimer.uniques)
971 listEnumerationTimer.uniques = uniques = { };
973 st = sqliteSearchTables[listEnumerationTimer.tablesIndex];
974 if(st.table && st.idField && st.searchFields && st.searchFields.count)
976 wordListPrepRowNum = 0;
977 wordListPrepRowCount = st.table.rowsCount;
979 if(st.table && st.idField && st.searchFields && st.searchFields.count &&
980 st.searchFields[0].field)
985 listEnumerationTimer.row = row = { st.table };
988 int len = searchString ? strlen(searchString) : 0;
989 searchCI.size = len + 1;
990 ConvertToASCII(searchString ? searchString : "", searchCI.array, &len, true);
991 searchCI.count = len + 1;
994 sprintf(queryString, "SELECT ROWID, * FROM `%s`", st.table.name);
995 strcat(queryString, " WHERE ");
996 for(sf : st.searchFields)
1001 strcat(queryString, " OR ");
1002 #define PERSON_SEARCH_LIKE
1003 // This code tries to implement the logic of PersonName::OnGetDataFromString because PersonName is inaccessible from here
1004 if(strstr(sf.field.type.name, "PersonName"))
1006 #ifdef PERSON_SEARCH_LIKE
1007 String ln = null, fn = null, mn = null;
1008 char * comma = strchr(searchCI.array, ',');
1011 int len = comma - searchCI.array;
1012 ln = new char[len + 1];
1013 memcpy(ln, searchCI.array, len);
1016 fn = CopyString(comma+1);
1019 for(c = strlen(fn)-2; c > 0; c--)
1023 mn = CopyString(fn + c + 1);
1030 ln = CopyString(searchCI.array);
1033 TrimLSpaces(ln, ln);
1034 TrimRSpaces(ln, ln);
1038 TrimLSpaces(fn, fn);
1039 TrimRSpaces(fn, fn);
1043 TrimLSpaces(mn, mn);
1044 TrimRSpaces(mn, mn);
1047 /* We could simply do this if we had access to PersonName: (don't forget the delete pn; below)
1049 pn.OnGetDataFromString(searchCI.array);
1051 if(ln && !fn && !mn)
1053 // Only last name is pecified in search object, it is looked for in all fields
1054 strcatf(queryString, "(PNLastName(`%s`) LIKE '%%%s%%' OR PNFirstName(`%s`) LIKE '%%%s%%' OR PNMiddleName(`%s`) LIKE '%%%s%%')",
1055 sf.field.name, searchCI.array, sf.field.name, searchCI.array, sf.field.name, ln);
1057 else if(ln || fn || mn)
1059 // Otherwise search is in respective fields only (all specified must match)
1061 strcatf(queryString, "(");
1064 if(!first) strcatf(queryString, " AND ");
1066 strcatf(queryString, "PNLastName(`%s`) LIKE '%%%s%%'", sf.field.name, ln);
1070 if(!first) strcatf(queryString, " AND ");
1072 strcatf(queryString, "PNFirstName(`%s`) LIKE '%%%s%%'", sf.field.name, fn);
1076 if(!first) strcatf(queryString, " AND ");
1078 strcatf(queryString, "PNMiddleName(`%s`) LIKE '%%%s%%'", sf.field.name, mn);
1080 strcatf(queryString, ")");
1083 delete ln; delete fn; delete mn;
1085 // To use CompPersonName::OnCompare:
1086 strcatf(queryString, "`%s` = ?", sf.field.name);
1090 strcatf(queryString, "`%s` LIKE '%%%s%%'", sf.field.name, searchString);
1094 PrintLn(queryString);
1095 startTime = GetTime();
1096 row.query = queryString;
1097 #ifndef PERSON_SEARCH_LIKE
1098 // To use CompPersonName::OnCompare:
1099 for(sf : st.searchFields)
1103 if(strstr(sf.field.type.name, "PersonName"))
1106 ((bool (*)(void *, void *, const char *))(void *)sf.field.type._vTbl[__ecereVMethodID_class_OnGetDataFromString])(sf.field.type, &pn, searchCI.array);
1107 row.SetQueryParamObject(++bindId, pn, sf.field.type);
1123 if(listFields && idField)
1125 // should we not start with a Next() ??? :S -- right now, query = does not need a Next(), unless it had parameters (SetQueryParam), See #591
1126 // when going through all the rows in a table you always start with Next() no?
1127 // is this different for query results?
1128 for(c = 0, next = !row.nil; c<slice && next; c++, next = row.Next())
1131 row.GetData(st.idField, id);
1132 //if(uniques[id]++ == 0)
1133 if(uniques[id] == 0)
1135 DataRow dataRow = list.AddRow();
1137 if(st.table == table)
1138 SetListRowFields(row, dataRow, true);
1139 else if(lookupRow.Find(idField, middle, nil, id))
1140 SetListRowFields(lookupRow, dataRow, true);
1144 uniques[id] = uniques[id] + 1;
1146 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1149 delete listEnumerationTimer.row; row = null;
1150 next = ++listEnumerationTimer.tablesIndex < sqliteSearchTables.count;
1153 else if(idField && stringField)
1155 // should we not start with a Next() ??? :S
1156 // when going through all the rows in a table you always start with Next() no?
1157 // is this different for query results?
1158 for(c = 0, next = !row.nil; c<slice && next; c++, next = row.Next())
1161 row.GetData(st.idField, id);
1162 //if(uniques[id]++ == 0)
1163 if(uniques[id] == 0)
1166 if(st.table == table)
1167 row.GetData(stringField, s);
1168 else if(lookupRow.Find(idField, middle, nil, id))
1169 lookupRow.GetData(stringField, s);
1172 list.AddString(s).tag = id;
1175 uniques[id] = uniques[id] + 1;
1177 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1180 delete listEnumerationTimer.row; row = null;
1181 next = ++listEnumerationTimer.tablesIndex < sqliteSearchTables.count;
1187 else if(!disabledFullListing)
1189 if(listFields && idField)
1191 for(c = 0; c<slice && (next = row.Next()); c++)
1194 DataRow dataRow = list.AddRow();
1195 row.GetData(idField, id);
1197 SetListRowFields(row, dataRow, true);
1198 //app.UpdateDisplay();
1200 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1202 else if(idField && stringField)
1204 for(c = 0; c<slice && (next = row.Next()); c++)
1208 row.GetData(idField, id);
1209 row.GetData(stringField, s);
1210 list.AddString(s).tag = id;
1213 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1217 list.Sort(listSortField, listSortOrder);
1221 listEnumerationTimer.hasCompleted = true;
1222 StopListEnumerationTimer();
1224 if(startTime) PrintLn("*** Search took ", (uint)((GetTime() - startTime) * 1000), "ms ***");
1229 void StopListEnumerationTimer()
1231 listEnumerationTimer.Stop();
1232 listEnumerationTimer.matchesIndex = 0;
1233 listEnumerationTimer.tablesIndex = 0;
1234 delete listEnumerationTimer.row;
1235 delete listEnumerationTimer.matches;
1236 delete listEnumerationTimer.uniques;
1239 WordListPrepTimer wordListPrepTimer
1241 userData = this, delay = 0.1f;
1245 Row row = wordListPrepTimer.row;
1246 static int slice = 512;
1247 static StringSearchTable st = null;
1249 static int wordListPrepRowCount = 0, wordListPrepRowNum = 0, ticks = 0;
1253 if(wordListPrepTimer.tablesIndex < searchTables.count)
1255 st = searchTables[wordListPrepTimer.tablesIndex];
1256 if(st.table && st.idField && st.searchFields && st.searchFields.count)
1258 wordListPrepRowNum = 0;
1259 wordListPrepRowCount = st.table.rowsCount;
1261 wordListPrepTimer.row = row = { st.table };
1262 DebugLn("building word list for ", st.table.name, " table ------------------------------------- ");
1269 Time delay = wordListPrepTimer.delay;
1270 Time lastTime = GetTime();
1274 PrintLn("indexing ", wordListPrepRowNum, " of ", wordListPrepRowCount, " --- slice is ", slice);
1276 for(c = 0; c<slice && (next = row.Next()); c++)
1279 row.GetData(st.idField, id);
1281 wordListPrepRowNum++;
1283 for(sf : st.searchFields)
1285 Field field = sf.field;
1286 StringSearchIndexingMethod method = sf.method;
1287 if(field && field.type == class(String))
1289 String string = null;
1290 row.GetData(field, string);
1292 if(string && string[0])
1293 ProcessWordListString(string, method, id);
1296 // todo: need to improve on this...
1297 // else ... call OnGetString of type ... etc...
1298 //PrintLn("todo: support other field types for string search");
1299 else if(field && field.type)
1301 char * n = field.name;
1302 char tempString[MAX_F_STRING];
1304 Class type = field.type;
1306 if(type.type == unitClass && !type.typeSize)
1308 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1312 if(type.type == structClass)
1313 data = (int64)new0 byte[type.structSize];
1314 ((bool (*)())(void *)row.GetData)(row, field, type, (type.type == structClass) ? (void *)data : &data);
1316 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1317 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)field.type._vTbl[__ecereVMethodID_class_OnGetString])(field.type, &data, tempString, null, null);
1319 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)field.type._vTbl[__ecereVMethodID_class_OnGetString])(field.type, (void *)data, tempString, null, null);
1322 ProcessWordListString(s, method, id);
1324 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1325 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1326 if(type.type == structClass)
1331 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1334 delete wordListPrepTimer.row; row = null;
1335 next = ++wordListPrepTimer.tablesIndex < searchTables.count;
1341 char filePath[MAX_FILENAME];
1344 sprintf(filePath, "%s.search", table.name);
1345 // this doesn't want to work? :S :S :S
1347 f = FileOpenBuffered(filePath, read);
1354 wordListPrepTimer.hasCompleted = true;
1355 StopWordListPrepTimer();
1361 void StopWordListPrepTimer()
1363 wordListPrepTimer.Stop();
1364 wordListPrepTimer.tablesIndex = 0;
1365 delete wordListPrepTimer.row;
1370 DebugLn("TableEditor::~()");
1372 fieldsBoxes.Free(); // TOCHECK: do I need to delete each to oppose the incref in AddFieldBox? -- Free() does just that
1373 delete searchString;
1378 delete dynamicLookupEditors;
1379 delete dynamicLookupTableEditors;
1380 if(searchTables) searchTables.Free();
1381 delete searchTables;
1382 if(sqliteSearchTables) sqliteSearchTables.Free();
1383 delete sqliteSearchTables;
1386 void ResetListFields()
1388 DebugLn("TableEditor::ResetListFields");
1389 if(list && listFields && listFields.count)
1391 bool c = list.created;
1393 for(lf : listFields)
1395 list.AddField(lf.dataField);
1396 incref lf.dataField;
1401 void AddTableEditor(TableEditor tableEditor)
1403 DebugLn("TableEditor::AddTableEditor");
1404 if(!tableEditors.Find(tableEditor))
1406 tableEditors.Add(tableEditor);
1410 DebugLn(" TableEditor instance already added");
1413 void RemoveTableEditor(TableEditor tableEditor)
1415 Iterator<TableEditor> it { tableEditors };
1416 DebugLn("TableEditor::RemoveTableEditor");
1417 if(it.Find(tableEditor))
1419 it.Remove(); // tableEditors.Remove(it.pointer); // <-- any reason why we would want to do that instead? -- It's the same.
1420 delete tableEditor; // AddTableEditor increfs...
1423 DebugLn(" TableEditor instance not found, no need to remove");
1426 void AddFieldBox(FieldBox fieldBox)
1428 // I was putting this here to force autosize on the lists (e.g. the Radiologists fields):
1430 if(!fieldBox.autoSize)
1431 fieldBox.autoSize = true;
1433 DebugLn("TableEditor::AddFieldBox");
1434 if(!fieldsBoxes.Find(fieldBox))
1436 fieldsBoxes.Add(fieldBox);
1442 DebugLn(" FieldBox instance already added");
1445 void RemoveFieldBox(FieldBox fieldBox)
1447 Iterator<FieldBox> it { fieldsBoxes };
1448 DebugLn("TableEditor::RemoveFieldBox");
1449 if(it.Find(fieldBox))
1451 it.Remove(); // fieldsBoxes.Remove(it.pointer); // <-- any reason why we would want to do that instead? -- It's the same.
1454 DebugLn(" FieldBox instance not found, no need to remove");
1457 void InitFieldsBoxes()
1459 DebugLn("TableEditor::InitFieldsBoxes");
1462 for(fb : fieldsBoxes)
1470 for(fb : fieldsBoxes)
1473 //NotifyInitFields(master, this);
1478 DebugLn("TableEditor::EditNew");
1480 modifiedDocument = false;
1485 DebugLn("TableEditor::EditSave");
1486 internalModifications = true;
1487 for(fb : fieldsBoxes)
1490 if(idField && list && listFields && listFields.count)
1492 DataRow listRow = list.currentRow;
1493 // ADDED THIS HERE FOR SQLITE TO REFRESH
1494 editRow.Find(idField, middle, nil, listRow.tag);
1495 SetListRowFields(editRow, listRow, false);
1496 list.Sort(listSortField, listSortOrder);
1498 internalModifications = false;
1500 for(te : tableEditors)
1503 modifiedDocument = false;
1509 Id selId = selectedId;
1510 DebugLn("TableEditor::EditLoad");
1514 internalModifications = true;
1520 lu.row = { lu.findIndex ? lu.findIndex : lu.findField.table };
1521 if(lu.valueField && eClass_IsDerived(lu.valueField.type, class(Id)) &&
1522 lu.findField && eClass_IsDerived(lu.findField.type, class(Id)))
1525 editRow.GetData(lu.valueField, id);
1526 lu.row.Find(lu.findField, middle, nil, id);
1530 for(fb : fieldsBoxes)
1532 OnCreateDynamicLookupEditors();
1533 internalModifications = false;
1534 size = size; // This seems to be required to fix autoSize on entering order screen
1536 DebugLn(" TODO: implement virtual method TableEditor::OnSubEditorsLoad");
1538 modifiedDocument = false;
1544 DebugLn("TableEditor::EditClear");
1546 internalModifications = true;
1547 for(fb : fieldsBoxes)
1549 for(e : dynamicLookupTableEditors)
1551 for(e : tableEditors)
1553 tableEditors.Free();
1554 dynamicLookupTableEditors.Free();
1555 //dynamicLookupTableEditors.size = 0;
1556 internalModifications = false;
1558 DebugLn(" TODO: remove all sub table editors");
1560 modifiedDocument = false;
1564 void SetListRowFields(Row dbRow, DataRow listRow, bool restoreSelection)
1566 // DebugLn("TableEditor::SetListRowFields");
1567 for(lf : listFields)
1569 if(lf.dataField && lf.field)
1571 if(eClass_IsDerived(lf.field.type, class(char*)))
1574 dbRow.GetData(lf.field, s);
1575 listRow.SetData(lf.dataField, s);
1578 else if(eClass_IsDerived(lf.field.type, class(Id)))
1584 dbRow.GetData(lf.field, id);
1585 s = lf.CustomLookup(id);
1586 listRow.SetData(lf.dataField, s);
1589 else if(lf.lookupFindField && (lf.lookupFindIndex || lf.lookupFindField.table) && lf.lookupValueField &&
1590 eClass_IsDerived(lf.lookupFindField.type, class(Id)) &&
1591 eClass_IsDerived(lf.lookupValueField.type, class(char*)))
1595 Row lookupRow { lf.lookupFindIndex ? lf.lookupFindIndex : lf.lookupFindField.table };
1596 dbRow.GetData(lf.field, id);
1597 if(lookupRow.Find(lf.lookupFindField, middle, nil, id))
1598 lookupRow.GetData(lf.lookupValueField, s);
1599 listRow.SetData(lf.dataField, s);
1604 else if(lf.CustomLookup && lf.field.type)
1606 char * n = lf.field.name;
1609 Class type = lf.field.type;
1610 if(type.type == unitClass && !type.typeSize)
1612 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1616 if(type.type == structClass)
1617 data = (int64)new0 byte[type.structSize];
1618 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1619 s = lf.CustomLookup((int)data);
1620 listRow.SetData(lf.dataField, s);
1621 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1622 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1623 if(type.type == structClass)
1627 else if(lf.field.type && eClass_IsDerived(lf.dataField.dataType, class(char*)))
1629 char * n = lf.field.name;
1630 char tempString[MAX_F_STRING];
1632 Class type = lf.field.type;
1634 if(type.type == unitClass && !type.typeSize)
1636 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1640 if(type.type == structClass)
1641 data = (int64)new0 byte[type.structSize];
1642 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1643 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1644 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)lf.field.type._vTbl[__ecereVMethodID_class_OnGetString])(lf.field.type, &data, tempString, null, null);
1646 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)lf.field.type._vTbl[__ecereVMethodID_class_OnGetString])(lf.field.type, (void*)data, tempString, null, null);
1648 listRow.SetData(lf.dataField, s);
1650 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1651 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1652 if(type.type == structClass)
1655 else if(lf.field.type)
1657 char * n = lf.field.name;
1658 //char tempString[256];
1660 Class type = lf.field.type;
1661 if(type.type == unitClass && !type.typeSize)
1663 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1667 if(type.type == structClass)
1668 data = (int64)new0 byte[type.structSize];
1669 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1670 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1671 listRow.SetData(lf.dataField, (void *)&data);
1673 listRow.SetData(lf.dataField, (void *)data);
1674 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1675 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1676 if(type.type == structClass)
1681 if(restoreSelection && !list.currentRow)
1684 if((select = list.FindRow(selectedId)))
1685 SelectListRow(select);
1689 Array<Id> SearchWordList()
1691 DebugLn("TableEditor::SearchWordList");
1692 #ifdef FULL_STRING_SEARCH
1698 WordEntry entries[256];
1699 Array<Id> results = null;
1700 if(searchTables && searchTables.count && searchString && searchString[0])
1702 char * searchCopy = CopyString(searchString);
1703 numTokens = TokenizeWith(searchCopy, sizeof(words) / sizeof(char *), words, " ',/-;[]{}", false);
1704 for(c = 0; c<numTokens; c++)
1706 len[c] = strlen(words[c]);
1708 entries[c] = (WordEntry)wordTree.FindString(words[c]);
1718 Map<Id, int> matches { };
1719 Map<Id, int> uniques { };
1720 MapNode<Id, int> mn;
1722 for(c = 0; c<numTokens; c++)
1724 if(entries[c] && entries[c].items && entries[c].items.count)
1726 for(i = 0; i<entries[c].items.count; i++)
1728 int count = uniques[entries[c].items.ids[i]];
1733 matches[entries[c].items.ids[i]]++;
1738 for(mn = matches.root.minimum; mn; mn = mn.next)
1741 results.Add(mn.key);
1747 else if(numTokens == 1)
1750 if(entries[0] && entries[0].items && entries[0].items.count)
1752 for(c = 0; c<entries[0].items.count; c++)
1753 results.Add(entries[0].items.ids[c]);
1765 // find a way to not load a tree for different searchFields
1766 // if the code that sets the searchFields has changed
1767 // store a search index signature containing following:
1768 // tables name, idField name and type, fields name and type
1769 void PrepareWordList()
1771 DebugLn("TableEditor::PrepareWordList");
1772 #ifdef FULL_STRING_SEARCH
1774 char filePath[MAX_FILENAME];
1777 sprintf(filePath, "%s.search", table.name);
1778 f = filePath ? FileOpenBuffered(filePath, read) : null;
1785 for(a = 0; a<26; a++)
1789 word[0] = 'a' + (char)a;
1792 letters[a] = (WordEntry)wordTree.FindString(word);
1793 for(b = 0; b<26; b++)
1795 word[1] = 'a' + (char)b;
1796 doubleLetters[a][b] = (WordEntry)wordTree.FindString(word);
1800 else if(searchTables && searchTables.count)
1805 for(a = 0; a<26; a++)
1809 word[0] = 'a' + (char)a;
1812 wordTree.Add((BTNode)(letters[a] = WordEntry { string = CopyString(word) }));
1813 for(b = 0; b<26; b++)
1815 word[1] = 'a' + (char)b;
1816 wordTree.Add((BTNode)(doubleLetters[a][b] = WordEntry { string = CopyString(word) }));
1821 wordListPrepTimer.tablesIndex = 0;
1822 wordListPrepTimer.Start();
1828 void ProcessWordListString(char * string, StringSearchIndexingMethod method, Id id)
1837 char asciiWord[1024];
1839 for(c = 0; ; c += nb)
1841 ch = UTF8GetChar(string + c, &nb);
1843 if(!ch || CharMatchCategories(ch, separators) ||
1844 (count && CharMatchCategories(ch, letters|numbers|marks|connector) != CharMatchCategories(lastCh, letters|numbers|marks|connector)))
1849 asciiWord[numChars] = 0;
1853 AddWord(word, count, method == allSubstrings, id);
1854 if(count > numChars)
1855 AddWord(asciiWord, strlen(asciiWord), method == allSubstrings, id);
1859 if(!CharMatchCategories(ch, separators))
1862 for(cc = 0; cc < nb; cc++)
1863 word[count++] = string[c + cc];
1865 asciiWord[numChars++] = ToASCII(ch);
1873 for(cc = 0; cc < nb; cc++)
1874 word[count++] = string[c + cc];
1876 asciiWord[numChars++] = ToASCII(ch);
1882 /*static */WordEntryBinaryTree wordTree
1884 CompareKey = (void *)BinaryTree::CompareString;
1885 FreeKey = BinaryTree::FreeString;
1888 void AddWord(char * word, int count, bool addAllSubstrings, Id id)
1890 //DebugLn("TableEditor::AddWord");
1891 #ifdef FULL_STRING_SEARCH
1893 if(addAllSubstrings)
1896 WordEntry mainEntry = null;
1897 WordEntry sEntry = null;
1899 for(s = 0; s < count; s += UTF8_NUM_BYTES(word[s]))
1904 WordEntry lEntry = null;
1905 memcpy(subWord, word + s, count-s);
1906 subWord[count-s] = 0; // THIS IS REQUIRED!! THE WHILE LOOP BELOW CHECKED count-s FIRST!!
1909 for(l = count-s; l>0; l--)
1912 WordEntry start = null, wordEntry;
1914 while(l > 0 && !UTF8_IS_FIRST(subWord[l])) l--;
1919 if(ch1 >= 'a' && ch1 <= 'z')
1921 char ch2 = subWord[1];
1922 if(count - s > 1 && ch2 >= 'a' && ch2 <= 'z')
1924 char ch2 = subWord[1];
1925 start = doubleLetters[ch1 - 'a'][ch2 - 'a'];
1929 start = letters[ch1 - 'a'];
1936 while(start && (max = (WordEntry)((BTNode)start).maximum))
1938 if(strcmp(max.string, subWord) >= 0)
1940 start = start.parent;
1945 start = (WordEntry)wordTree.root;
1947 if((wordEntry = (WordEntry)((BTNode)start).FindString(subWord)))
1953 wordTree.Add((BTNode)(wordEntry = WordEntry { string = CopyString(subWord) }));
1957 mainEntry = wordEntry;
1965 if(!wordEntry.words) wordEntry.words = IdList { };
1966 wordEntry.words.Add((Id)mainEntry);
1971 if(!wordEntry.words) wordEntry.words = IdList { };
1972 wordEntry.words.Add((Id)sEntry);
1976 if(!wordEntry.words) wordEntry.words = IdList { };
1977 wordEntry.words.Add((Id)lEntry);
1979 if(!wordEntry.items) wordEntry.items = IdList { };
1980 wordEntry.items.Add(id);
1986 WordEntry wordEntry;
1987 if(!(wordEntry = (WordEntry)(wordTree.root).FindString(word)))
1988 wordTree.Add((BTNode)(wordEntry = WordEntry { string = CopyString(word) }));
1989 if(!wordEntry.items) wordEntry.items = IdList { };
1990 wordEntry.items.Add(id);
1997 public class ListField : struct
2001 DataField dataField;
2002 Field lookupFindField;
2003 Field lookupValueField;
2004 Table lookupFindIndex;
2005 String (*CustomLookup)(Id);
2030 public class LookupEditor : struct
2033 subclass(TableEditor) editorClass;
2034 Window parentWindow;
2035 Field lookupValueField;
2036 Field lookupFindField;
2037 Field lookupIdField;
2038 Table lookupFindIndex;
2041 // all methods currently perform ascii conversion and all that jazz on every string added to the index
2042 public enum StringSearchIndexingMethod { fullString, allSubstrings };
2044 public class StringSearchField
2048 StringSearchIndexingMethod method;
2050 Field lookupFindField;
2051 Field lookupValueField;
2053 //String (*CustomRead)(Id);
2056 public class StringSearchTable
2061 Array<StringSearchField> searchFields;
2064 ~StringSearchTable()
2066 delete searchFields;
2070 public class SQLiteSearchField
2074 //StringSearchIndexingMethod method;
2077 public class SQLiteSearchTable
2082 Array<SQLiteSearchField> searchFields;
2085 ~SQLiteSearchTable()
2087 delete searchFields;
2091 static WordEntry * btnodes;
2093 struct WordEntryBinaryTree : BinaryTree
2095 WordEntry * entries;
2097 void OnSerialize(IOChannel channel)
2101 uint count = this.count;
2102 DebugLn("WordEntryBinaryTree::OnSerialize");
2103 for(id = 1, node = (WordEntry)root; node;)
2110 else if(node.parent)
2112 bool isLeft = node == node.parent.left;
2117 if(isLeft && node.right)
2123 isLeft = node == node.parent.left;
2132 channel.Serialize(id);
2133 channel.Serialize((WordEntry)root);
2136 void OnUnserialize(IOChannel channel)
2138 WordEntry root, node;
2140 DebugLn("WordEntryBinaryTree::OnUnserialize");
2141 channel.Unserialize(count);
2142 entries = new WordEntry[count];
2144 channel.Unserialize(root);
2145 this.root = (BTNode)root;
2146 // count = root ? this.root.count : 0;
2148 for(node = (WordEntry)root; node;)
2153 for(c = 0; c<node.words.count; c++)
2154 node.words.ids[c] = (Id)btnodes[node.words.ids[c] - 1];
2160 else if(node.parent)
2162 bool isLeft = node == node.parent.left;
2167 if(isLeft && node.right)
2173 isLeft = node == node.parent.left;
2185 class WordEntry : struct
2189 WordEntry left, right;
2202 void OnSerialize(IOChannel channel)
2204 #ifdef FULL_STRING_SEARCH
2207 channel.Serialize(id);
2208 channel.Serialize(string);
2209 channel.Serialize(items);
2214 channel.Serialize(words.count);
2215 for(c = 0; c < words.count; c++)
2217 uint id = ((WordEntry)words.ids[c]).id;
2218 channel.Serialize(id);
2224 channel.Serialize(none);
2227 // channel.Serialize(words);
2228 channel.Serialize(left);
2229 channel.Serialize(right);
2234 channel.Serialize(nothing);
2239 void OnUnserialize(IOChannel channel)
2241 #ifdef FULL_STRING_SEARCH
2243 channel.Unserialize(id);
2248 // TODO: Fix typed_object issues
2249 entry = btnodes[id - 1] = eInstance_New(class(WordEntry));
2250 this = (void *)entry;
2252 channel.Unserialize(string);
2253 channel.Unserialize(items);
2254 channel.Unserialize(words);
2256 channel.Unserialize(left);
2257 if(left) { left.parent = (void *)this; }
2258 channel.Unserialize(right);
2259 if(right) { right.parent = (void *)this; }
2261 // TODO: Precomp errors without extra brackets
2262 depth = ((BTNode)((void *)this)).depthProp;
2271 class ListEnumerationTimer : Timer
2279 Map<Id, int> uniques;
2282 class WordListPrepTimer : Timer
2290 class EnumerateThread : Thread
2309 bool abort, abortNow;
2316 //if(app.ProcessInput(true))
2319 Row r { editor.table };
2323 if(editor.listFields && editor.idField)
2325 /*for(c=0; c<matches.count && !abort; c++)
2327 if(r.Find(editor.idField, middle, nil, matches[c]))
2332 row = editor.list.AddRow();
2333 r.GetData(editor.idField, id);
2335 editor.SetListRowFields(r, row, true);
2339 DebugLn($"WordList match cannot be found in database.");
2342 else if(editor.idField && editor.stringField)
2344 /*for(c=0; c<matches.count && !abort; c++)
2346 if(r.Find(editor.idField, middle, nil, matches[c]))
2350 r.GetData(editor.idField, id);
2351 r.GetData(editor.stringField, s);
2353 editor.list.AddString(s).tag = id;
2358 DebugLn($"WordList match cannot be found in database.");
2364 else if(!editor.disabledFullListing)
2366 if(editor.listFields && editor.idField)
2369 while(r.Next() && !abort)
2374 r.GetData(editor.idField, id);
2375 //if(app.ProcessInput(true))
2379 row = editor.list.AddRow();
2381 editor.SetListRowFields(r, row, true);
2386 else if(editor.idField && editor.stringField)
2388 /*while(r.Next() && !abort)
2393 r.GetData(editor.idField, id);
2394 r.GetData(editor.stringField, s);
2395 editor.list.AddString(s).tag = id;
2407 editor.list.Sort(editor.listSortField, editor.listSortOrder);
2426 editor.list.Update(null);
2427 //app.Wait(); // Sleep(0.2f);
2428 //if(app.ProcessInput(true))
2431 //app.UpdateDisplay();
2438 static define app = ((GuiApplication)__thisModule);
2440 static inline void DebugLn(typed_object object, ...)
2442 #if defined(_DEBUG_LINE)
2445 va_start(args, object);
2446 PrintStdArgsToBuffer(buffer, sizeof(buffer), object, args);