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
144 DebugLn("TableEditor::table|set");
154 DebugLn("TableEditor::index|set");
156 filterRow.tbl = index;
163 DebugLn("TableEditor::OnPostCreate");
171 InitFieldsBoxes(); // IMPORTANT: table must be set *AFTER* all related FieldEditors have been initialized
173 Field fldId = idField, fldName = stringField, fldActive = null;
174 FieldIndex indexedFields[1];
175 if(!idField) fldId = table.FindField(defaultIdField);
176 if(!fldName) fldName = table.FindField(defaultNameField);
177 if(!fldActive) fldActive = table.FindField(defaultActiveField);
178 indexedFields[0] = { fldId };
179 table.Index(1, indexedFields);
187 if(!listEnumerationTimer.hasCompleted)
189 if(list && !list.currentRow)
190 list.SelectRow(list.firstRow); // should the tableeditor select method be used here?
195 bool OnClose(bool parentClosing)
197 bool result = NotifyClosing();
206 property ListBox list
210 DebugLn("TableEditor::list|set");
217 Field indexFilterField;
220 property Array<ListField> listFields
224 DebugLn("TableEditor::listFields|set");
229 Array<ListField> listFields;
231 DataField listSortField;
232 bool disabledFullListing;
234 property Array<StringSearchField> searchFields
238 StringSearchTable searchTable { table, idField, value };
239 DebugLn("TableEditor::searchFields|set");
240 // The searchTables property will incref...
241 property::searchTables = { [ searchTable ] };
245 property Array<StringSearchTable> searchTables
249 // This API is not very clear on ownership of search tables array/search table/field...
250 // Right now both the array and tables themselves are incref'ed here
252 for(t : value) { incref t; }
253 DebugLn("TableEditor::searchTables|set");
254 if(searchTables) searchTables.Free();
256 searchTables = value;
259 Array<StringSearchTable> searchTables;
261 property Array<SQLiteSearchTable> sqliteSearchTables
266 for(t : value) { incref t; }
267 DebugLn("TableEditor::searchTables|set");
268 if(sqliteSearchTables) sqliteSearchTables.Free();
269 delete sqliteSearchTables;
270 sqliteSearchTables = value;
273 Array<SQLiteSearchTable> sqliteSearchTables;
275 property String searchString
279 bool modified = modifiedDocument;
280 DebugLn("TableEditor::searchString|set");
281 switch(modified ? OnLeavingModifiedDocument() : no)
291 if(value && value[0])
292 searchString = CopyString(value);
300 Map<Table, Lookup> lookups;
302 Array<LookupEditor> dynamicLookupEditors;
303 property Array<LookupEditor> dynamicLookupEditors
307 DebugLn("TableEditor::dynamicLookupEditors|set");
308 dynamicLookupEditors = value;
313 property Id selectedId { get { return selectedId; } }
315 Array<FieldBox> fieldsBoxes { };
316 Array<TableEditor> tableEditors { };
317 Array<TableEditor> dynamicLookupTableEditors { };
321 public virtual void OnInitialize();
322 public virtual void OnLoad();
323 public virtual void OnStateChanged();
324 bool internalModifications;
325 public void NotifyModifiedDocument()
327 DebugLn("TableEditor::NotifyModifiedDocument");
328 if(!internalModifications)
332 //public virtual bool Window::NotifyNew(AltListSection listSection, Row r);
333 //virtual void Window::NotifyInitFields(AltEditSection editSection);
335 public virtual DialogResult OnLeavingModifiedDocument()
337 DebugLn("TableEditor::OnLeavingModifiedDocument");
338 return MessageBox { master = this, type = yesNoCancel, text = text && text[0] ? text : $"Table Editor",
339 contents = $"You have modified this entry. Would you like to save it before proceeding?"
343 public virtual bool OnRemovalRequest()
345 DebugLn("TableEditor::OnRemovalRequest");
346 return MessageBox { master = this, type = yesNo, text = text && text[0] ? text : $"Table Editor",
347 contents = $"You are about to permanently remove an entry.\n"
348 "Do you wish to continue?"
352 //public virtual void Window::NotifyDeleting(ListSection listSection);
353 //public virtual void Window::NotifyDeleted(ListSection listSection);
355 public bool NotifyClosing()
358 DebugLn("TableEditor::NotifyClosing");
361 switch(OnLeavingModifiedDocument())
375 StopWordListPrepTimer();
376 StopListEnumerationTimer();
381 //public void List() // this gets called out of nowhere by some constructor thing...
382 public void Enumerate()
384 DebugLn("TableEditor::Enumerate");
387 StopListEnumerationTimer();
391 if(list || OnList != TableEditor::OnList)
394 Array<Id> matches = null;
395 listEnumerationTimer.sqliteSearch = false;
396 if(searchTables && searchTables.count)
397 matches = SearchWordList();
398 //else if(sqliteSearchTables && sqliteSearchTables.count)
399 //matches = SearchSQLite();
400 else if(searchString && searchString[0] &&
401 sqliteSearchTables && sqliteSearchTables.count &&
402 sqliteSearchTables[0].table && sqliteSearchTables[0].idField &&
403 sqliteSearchTables[0].searchFields && sqliteSearchTables[0].searchFields.count &&
404 sqliteSearchTables[0].searchFields[0].field)
405 listEnumerationTimer.sqliteSearch = true;
406 if(matches && matches.count)
407 PrintLn("results count: ", matches.count);
412 modifiedDocument = false; // setting this here is not really logical, enumeration and modified have nothing to do with eachother
415 virtual void OnList(Row r, Array<Id> matches)
417 DebugLn("TableEditor::OnList");
418 if(!listEnumerationTimer.started)
420 listEnumerationTimer.hasCompleted = false;
421 listEnumerationTimer.matchesIndex = 0;
422 listEnumerationTimer.tablesIndex = 0;
423 if(!listEnumerationTimer.sqliteSearch)
424 listEnumerationTimer.row = Row { r.tbl };
427 listEnumerationTimer.matches = { };
428 // TOFIX: This (void *) cast here should NOT be required... Fix this Container warning:
429 // warning: incompatible expression matches (ecere::com::Array<eda::Id>); expected ecere::com::Container<T>
430 listEnumerationTimer.matches.Copy((void *)matches);
433 listEnumerationTimer.matches = null;
434 listEnumerationTimer.Start();
437 DebugLn("TableEditor::OnList -- timer state error");
440 virtual void OnCreateDynamicLookupEditors()
442 DebugLn("TableEditor::OnCreateLookupEditors");
443 if(dynamicLookupEditors && dynamicLookupEditors.count)
445 for(f : dynamicLookupEditors)
447 if(f.editorClass && f.parentWindow && f.lookupFindField)
449 Row row { f.lookupFindIndex ? f.lookupFindIndex : f.lookupFindField.table };
450 // todo: make this work for all types
452 editRow.GetData(f.lookupValueField, id);
453 // TODO: add alternative class instance for creation when no rows are found via lookup
454 for(row.Find(f.lookupFindField, middle, nil, id); !row.nil; row.Next())
456 // todo: make this work for all types, although this is meant to be an id field
458 TableEditor editor = eInstance_New(f.editorClass);
460 editor.parent = f.parentWindow;
461 editor.master = this;
462 dynamicLookupTableEditors.Add(editor);
464 row.GetData(f.lookupIdField, id);
473 TableEditor masterEditor;
475 public property TableEditor masterEditor
479 if(value != masterEditor)
482 masterEditor.RemoveTableEditor(this);
483 masterEditor = value;
485 value.AddTableEditor(this);
492 if(eClass_IsDerived(parent._class, class(TableEditor)))
493 property::masterEditor = (TableEditor)parent;
498 if(eClass_IsDerived(master._class, class(TableEditor)))
499 property::masterEditor = (TableEditor)master;
504 DebugLn("TableEditor::CreateRow");
505 //list.NotifySelect(this, list, null, 0);
506 if(table && editRow && editRow.tbl && !modifiedDocument)
508 uint id; // = table.rowsCount + 1; // this is bad with deleted rows, won't work, how to have unique id?
509 // I think the 3 following comment lines apply to the old sqlite driver before many fix we done for wsms
510 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...
511 //Row r { editRow.tbl }; // for example, Row::Last() here is not using the proper sqlite statement and fails to
512 // return false when no rows are present in a table
516 /*uint count = editRow.tbl.GetRowsCount();
519 // r.Last() is returning true even if there are not rows in this table (SQLite)
520 if(count && !(r.Last() || r.Last()))
521 DebugLn("PROBLEM");*/
522 if(r.Last()) // this will reuse ids in cases where the item(s) with the last id have been deleted
524 r.GetData(idField, id);
535 // Patch for SQLite driver which auto-increments IDs
537 if(r.GetData(idField, curId))
540 r.SetData(idField, id);
543 r.SetData(fldActive, active);*/
547 newText = PrintString("[", newEntryStringDebug, id, "]");
549 newText = PrintString("[", newEntryString, "]");
552 //if(NotifyNew(master, this, r))
553 if(listFields && idField)
557 if(lf.dataField && lf.field)
559 if(lf.field.type == class(String))
560 r.SetData(lf.field, newText);
561 else // this whole block is new?
563 if(lf.field.type._vTbl[__ecereVMethodID_class_OnGetDataFromString])
565 Class dataType = lf.field.type;
566 int64 dataHolder = 0;
567 void * data = &dataHolder;
569 if(dataType && dataType.type == structClass)
571 dataHolder = (int64)new0 byte[dataType.structSize];
572 data = (void *)dataHolder;
574 /*else if(dataType && (dataType.type == noHeadClass || dataType.type == normalClass))
576 if(eClass_IsDerived(dataType, class(char*)))
577 dataHolder = (int64)CopyString("");
579 dataHolder = (int64)eInstance_New(dataType);
580 data = (void *)&dataHolder;
588 ((bool (*)(void *, void *, const char *))(void *)dataType._vTbl[__ecereVMethodID_class_OnGetDataFromString])(dataType, data, newText);
591 /*((void (*)(void *, void *))(void *)dataType._vTbl[__ecereVMethodID_class_OnFree])(dataType, dataHolder);
592 if(dataType.type == structClass)
594 void * dataPtr = (void *)dataHolder;
606 // have a better technique than Row::Next(); Row::Find(...); to make sure Row::GetData() will work right after a Row::SetData()?
607 // it seems we're missing Row::Update()
612 // next line is a patch for SQLite not returning data from GetData right after a SetData
613 if(idField && r.Find(idField, middle, nil, id))
614 SetListRowFields(r, row, false);
617 else if(idField && stringField)
619 r.SetData(stringField, newText);
622 row = list.AddString(newText);
632 list.Sort(listSortField, listSortOrder);
633 if(row) SelectListRow(row);
641 DebugLn("TableEditor::Remove");
642 if(editRow.sysID) //list && list.currentRow)
644 if(OnRemovalRequest())
648 list.DeleteRow(list.currentRow);
650 //NotifyDeleted(master, this);
652 SelectListRow(list.currentRow);
660 DebugLn("TableEditor::Load");
666 DebugLn("TableEditor::Write");
670 bool ListSelect(DataRow row)
673 DebugLn("TableEditor::ListSelect");
674 if(/*-row && -*/row != lastRow)
680 list.currentRow = lastRow;
682 switch(OnLeavingModifiedDocument())
690 list.currentRow = row;
694 if(list.currentRow == row)
703 DebugLn("TableEditor::Select");
704 // 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)
705 if(idField && editRow && (editRow.sysID = id, !editRow.nil))// && editRow.Find(idField, middle, nil, id))
707 selectedId = editRow.sysID;
719 DebugLn("TableEditor::Filter");
720 if(selectedId && index && indexFilterField)
722 for(filterRow.Find(indexFilterField, middle, nil, id); !filterRow.nil; filterRow.Next())
725 filterRow.GetData(idField, id2);
726 if(id2 == selectedId)
739 bool SelectNext(bool loopAround)
741 bool result = NotifyClosing();
742 bool wasNil = !editRow.sysID;
743 DebugLn("TableEditor::SelectNext");
748 if(!filterRow.Next() && loopAround)
750 //filterRow.First(); // Row::First doesn't behave properly in a filtered table
751 while(filterRow.Previous())
757 if(wasNil && filterRow.sysID == selectedId)
758 filterRow.Next(); // this whole wasNil thing makes no sense to me?
759 editRow.sysID = filterRow.sysID;
766 if(!editRow.Next() && loopAround)
771 selectedId = editRow.sysID;
780 bool SelectPrevious(bool loopAround)
782 bool result = NotifyClosing();
783 bool wasNil = !editRow.sysID;
784 DebugLn("TableEditor::SelectPrevious");
789 if(!filterRow.Previous() && loopAround)
791 //filterRow.Last(); // Row::Last doesn't behave properly in a filtered table
792 while(filterRow.Next())
794 filterRow.Previous();
798 if(wasNil && filterRow.sysID == selectedId)
799 filterRow.Previous(); // this whole wasNil thing makes no sense to me?
800 editRow.sysID = filterRow.sysID;
807 if(!editRow.Previous() && loopAround)
812 selectedId = editRow.sysID;
821 void SelectListRow(DataRow row)
823 // Time startTime = GetTime();
824 DebugLn("TableEditor::SelectListRow");
827 // 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)
828 selectedId = (Id)row.tag;
831 if(list.currentRow != row)
832 list.currentRow = row;
833 if(idField && editRow.Find(idField, middle, nil, selectedId))
836 //NotifySelectListRow(master, this, selectedId);
840 // Logf("SelectListRow took %f seconds\n", GetTime() - startTime);
850 Array<char> searchCI { };
852 ListEnumerationTimer listEnumerationTimer
854 userData = this, delay = 0.1f;
857 static Time startTime;
860 Row row = listEnumerationTimer.row;
861 Array<Id> matches = listEnumerationTimer.matches;
862 Time delay = listEnumerationTimer.delay;
863 Time lastTime = GetTime();
864 static int slice = 128;
866 static int wordListPrepRowCount = 0, wordListPrepRowNum = 0, ticks = 0;
869 PrintLn("listing... ");
873 int index = listEnumerationTimer.matchesIndex;
874 if(listFields && idField)
876 for(c=0; c<slice && (next = index++<matches.count); c++)
878 if(row.Find(idField, middle, nil, matches[index]))
881 DataRow dataRow = list.AddRow();
882 row.GetData(idField, id);
884 SetListRowFields(row, dataRow, true);
887 DebugLn($"WordList match cannot be found in database.");
889 listEnumerationTimer.matchesIndex = index;
890 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
892 else if(idField && stringField)
894 for(c=0; c<slice && (next = index++<matches.count); c++)
896 if(row.Find(idField, middle, nil, matches[index]))
900 row.GetData(idField, id);
901 row.GetData(stringField, s);
902 list.AddString(s).tag = id;
906 DebugLn($"WordList match cannot be found in database.");
908 listEnumerationTimer.matchesIndex = index;
909 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
912 else if(listEnumerationTimer.sqliteSearch)
914 static SQLiteSearchTable st = null;
915 Row lookupRow { table };
916 Map<Id, int> uniques = listEnumerationTimer.uniques;
919 if(listEnumerationTimer.tablesIndex < sqliteSearchTables.count)
921 char queryString[4096*4];
923 if(!listEnumerationTimer.uniques)
924 listEnumerationTimer.uniques = uniques = { };
926 st = sqliteSearchTables[listEnumerationTimer.tablesIndex];
927 if(st.table && st.idField && st.searchFields && st.searchFields.count)
929 wordListPrepRowNum = 0;
930 wordListPrepRowCount = st.table.rowsCount;
932 if(st.table && st.idField && st.searchFields && st.searchFields.count &&
933 st.searchFields[0].field)
938 listEnumerationTimer.row = row = { st.table };
941 int len = searchString ? strlen(searchString) : 0;
942 searchCI.size = len + 1;
943 ConvertToASCII(searchString ? searchString : "", searchCI.array, &len, true);
944 searchCI.count = len + 1;
947 sprintf(queryString, "SELECT ROWID, * FROM `%s`", st.table.name);
948 strcat(queryString, " WHERE ");
949 for(sf : st.searchFields)
954 strcat(queryString, " OR ");
955 #define PERSON_SEARCH_LIKE
956 // This code tries to implement the logic of PersonName::OnGetDataFromString because PersonName is inaccessible from here
957 if(strstr(sf.field.type.name, "PersonName"))
959 #ifdef PERSON_SEARCH_LIKE
960 String ln = null, fn = null, mn = null;
961 char * comma = strchr(searchCI.array, ',');
964 int len = comma - searchCI.array;
965 ln = new char[len + 1];
966 memcpy(ln, searchCI.array, len);
969 fn = CopyString(comma+1);
972 for(c = strlen(fn)-2; c > 0; c--)
976 mn = CopyString(fn + c + 1);
983 ln = CopyString(searchCI.array);
1000 /* We could simply do this if we had access to PersonName: (don't forget the delete pn; below)
1002 pn.OnGetDataFromString(searchCI.array);
1004 if(ln && !fn && !mn)
1006 // Only last name is pecified in search object, it is looked for in all fields
1007 strcatf(queryString, "(PNLastName(`%s`) LIKE '%%%s%%' OR PNFirstName(`%s`) LIKE '%%%s%%' OR PNMiddleName(`%s`) LIKE '%%%s%%')",
1008 sf.field.name, searchCI.array, sf.field.name, searchCI.array, sf.field.name, ln);
1010 else if(ln || fn || mn)
1012 // Otherwise search is in respective fields only (all specified must match)
1014 strcatf(queryString, "(");
1017 if(!first) strcatf(queryString, " AND ");
1019 strcatf(queryString, "PNLastName(`%s`) LIKE '%%%s%%'", sf.field.name, ln);
1023 if(!first) strcatf(queryString, " AND ");
1025 strcatf(queryString, "PNFirstName(`%s`) LIKE '%%%s%%'", sf.field.name, fn);
1029 if(!first) strcatf(queryString, " AND ");
1031 strcatf(queryString, "PNMiddleName(`%s`) LIKE '%%%s%%'", sf.field.name, mn);
1033 strcatf(queryString, ")");
1036 delete ln; delete fn; delete mn;
1038 // To use CompPersonName::OnCompare:
1039 strcatf(queryString, "`%s` = ?", sf.field.name);
1043 strcatf(queryString, "`%s` LIKE '%%%s%%'", sf.field.name, searchString);
1047 PrintLn(queryString);
1048 startTime = GetTime();
1049 row.query = queryString;
1050 #ifndef PERSON_SEARCH_LIKE
1051 // To use CompPersonName::OnCompare:
1052 for(sf : st.searchFields)
1056 if(strstr(sf.field.type.name, "PersonName"))
1059 ((bool (*)(void *, void *, const char *))(void *)sf.field.type._vTbl[__ecereVMethodID_class_OnGetDataFromString])(sf.field.type, &pn, searchCI.array);
1060 row.SetQueryParamObject(++bindId, pn, sf.field.type);
1076 if(listFields && idField)
1078 // should we not start with a Next() ??? :S -- right now, query = does not need a Next(), unless it had parameters (SetQueryParam), See #591
1079 // when going through all the rows in a table you always start with Next() no?
1080 // is this different for query results?
1081 for(c = 0, next = !row.nil; c<slice && next; c++, next = row.Next())
1084 row.GetData(st.idField, id);
1085 //if(uniques[id]++ == 0)
1086 if(uniques[id] == 0)
1088 DataRow dataRow = list.AddRow();
1090 if(st.table == table)
1091 SetListRowFields(row, dataRow, true);
1092 else if(lookupRow.Find(idField, middle, nil, id))
1093 SetListRowFields(lookupRow, dataRow, true);
1097 uniques[id] = uniques[id] + 1;
1099 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1102 delete listEnumerationTimer.row; row = null;
1103 next = ++listEnumerationTimer.tablesIndex < sqliteSearchTables.count;
1106 else if(idField && stringField)
1108 // should we not start with a Next() ??? :S
1109 // when going through all the rows in a table you always start with Next() no?
1110 // is this different for query results?
1111 for(c = 0, next = !row.nil; c<slice && next; c++, next = row.Next())
1114 row.GetData(st.idField, id);
1115 //if(uniques[id]++ == 0)
1116 if(uniques[id] == 0)
1119 if(st.table == table)
1120 row.GetData(stringField, s);
1121 else if(lookupRow.Find(idField, middle, nil, id))
1122 lookupRow.GetData(stringField, s);
1125 list.AddString(s).tag = id;
1128 uniques[id] = uniques[id] + 1;
1130 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1133 delete listEnumerationTimer.row; row = null;
1134 next = ++listEnumerationTimer.tablesIndex < sqliteSearchTables.count;
1140 else if(!disabledFullListing)
1142 if(listFields && idField)
1144 for(c = 0; c<slice && (next = row.Next()); c++)
1147 DataRow dataRow = list.AddRow();
1148 row.GetData(idField, id);
1150 SetListRowFields(row, dataRow, true);
1151 //app.UpdateDisplay();
1153 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1155 else if(idField && stringField)
1157 for(c = 0; c<slice && (next = row.Next()); c++)
1161 row.GetData(idField, id);
1162 row.GetData(stringField, s);
1163 list.AddString(s).tag = id;
1166 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1170 list.Sort(listSortField, listSortOrder);
1174 listEnumerationTimer.hasCompleted = true;
1175 StopListEnumerationTimer();
1177 if(startTime) PrintLn("*** Search took ", (uint)((GetTime() - startTime) * 1000), "ms ***");
1182 void StopListEnumerationTimer()
1184 listEnumerationTimer.Stop();
1185 listEnumerationTimer.matchesIndex = 0;
1186 listEnumerationTimer.tablesIndex = 0;
1187 delete listEnumerationTimer.row;
1188 delete listEnumerationTimer.matches;
1189 delete listEnumerationTimer.uniques;
1192 WordListPrepTimer wordListPrepTimer
1194 userData = this, delay = 0.1f;
1198 Row row = wordListPrepTimer.row;
1199 static int slice = 512;
1200 static StringSearchTable st = null;
1202 static int wordListPrepRowCount = 0, wordListPrepRowNum = 0, ticks = 0;
1206 if(wordListPrepTimer.tablesIndex < searchTables.count)
1208 st = searchTables[wordListPrepTimer.tablesIndex];
1209 if(st.table && st.idField && st.searchFields && st.searchFields.count)
1211 wordListPrepRowNum = 0;
1212 wordListPrepRowCount = st.table.rowsCount;
1214 wordListPrepTimer.row = row = { st.table };
1215 DebugLn("building word list for ", st.table.name, " table ------------------------------------- ");
1222 Time delay = wordListPrepTimer.delay;
1223 Time lastTime = GetTime();
1227 PrintLn("indexing ", wordListPrepRowNum, " of ", wordListPrepRowCount, " --- slice is ", slice);
1229 for(c = 0; c<slice && (next = row.Next()); c++)
1232 row.GetData(st.idField, id);
1234 wordListPrepRowNum++;
1236 for(sf : st.searchFields)
1238 Field field = sf.field;
1239 StringSearchIndexingMethod method = sf.method;
1240 if(field && field.type == class(String))
1242 String string = null;
1243 row.GetData(field, string);
1245 if(string && string[0])
1246 ProcessWordListString(string, method, id);
1249 // todo: need to improve on this...
1250 // else ... call OnGetString of type ... etc...
1251 //PrintLn("todo: support other field types for string search");
1252 else if(field && field.type)
1254 char * n = field.name;
1255 char tempString[MAX_F_STRING];
1257 Class type = field.type;
1259 if(type.type == unitClass && !type.typeSize)
1261 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1265 if(type.type == structClass)
1266 data = (int64)new0 byte[type.structSize];
1267 ((bool (*)())(void *)row.GetData)(row, field, type, (type.type == structClass) ? (void *)data : &data);
1269 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1270 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)field.type._vTbl[__ecereVMethodID_class_OnGetString])(field.type, &data, tempString, null, null);
1272 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)field.type._vTbl[__ecereVMethodID_class_OnGetString])(field.type, (void *)data, tempString, null, null);
1275 ProcessWordListString(s, method, id);
1277 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1278 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1279 if(type.type == structClass)
1284 if(next) slice = Max(32, (int)(slice * (delay / (GetTime() - lastTime))));
1287 delete wordListPrepTimer.row; row = null;
1288 next = ++wordListPrepTimer.tablesIndex < searchTables.count;
1294 char filePath[MAX_FILENAME];
1297 sprintf(filePath, "%s.search", table.name);
1298 // this doesn't want to work? :S :S :S
1300 f = FileOpenBuffered(filePath, read);
1307 wordListPrepTimer.hasCompleted = true;
1308 StopWordListPrepTimer();
1314 void StopWordListPrepTimer()
1316 wordListPrepTimer.Stop();
1317 wordListPrepTimer.tablesIndex = 0;
1318 delete wordListPrepTimer.row;
1323 DebugLn("TableEditor::~()");
1325 fieldsBoxes.Free(); // TOCHECK: do I need to delete each to oppose the incref in AddFieldBox? -- Free() does just that
1326 delete searchString;
1331 delete dynamicLookupEditors;
1332 delete dynamicLookupTableEditors;
1333 if(searchTables) searchTables.Free();
1334 delete searchTables;
1335 if(sqliteSearchTables) sqliteSearchTables.Free();
1336 delete sqliteSearchTables;
1339 void ResetListFields()
1341 DebugLn("TableEditor::ResetListFields");
1342 if(list && listFields && listFields.count)
1344 bool c = list.created;
1346 for(lf : listFields)
1348 list.AddField(lf.dataField);
1349 incref lf.dataField;
1354 void AddTableEditor(TableEditor tableEditor)
1356 DebugLn("TableEditor::AddTableEditor");
1357 if(!tableEditors.Find(tableEditor))
1359 tableEditors.Add(tableEditor);
1363 DebugLn(" TableEditor instance already added");
1366 void RemoveTableEditor(TableEditor tableEditor)
1368 Iterator<TableEditor> it { tableEditors };
1369 DebugLn("TableEditor::RemoveTableEditor");
1370 if(it.Find(tableEditor))
1372 it.Remove(); // tableEditors.Remove(it.pointer); // <-- any reason why we would want to do that instead? -- It's the same.
1373 delete tableEditor; // AddTableEditor increfs...
1376 DebugLn(" TableEditor instance not found, no need to remove");
1379 void AddFieldBox(FieldBox fieldBox)
1381 // I was putting this here to force autosize on the lists (e.g. the Radiologists fields):
1383 if(!fieldBox.autoSize)
1384 fieldBox.autoSize = true;
1386 DebugLn("TableEditor::AddFieldBox");
1387 if(!fieldsBoxes.Find(fieldBox))
1389 fieldsBoxes.Add(fieldBox);
1395 DebugLn(" FieldBox instance already added");
1398 void RemoveFieldBox(FieldBox fieldBox)
1400 Iterator<FieldBox> it { fieldsBoxes };
1401 DebugLn("TableEditor::RemoveFieldBox");
1402 if(it.Find(fieldBox))
1404 it.Remove(); // fieldsBoxes.Remove(it.pointer); // <-- any reason why we would want to do that instead? -- It's the same.
1407 DebugLn(" FieldBox instance not found, no need to remove");
1410 void InitFieldsBoxes()
1412 DebugLn("TableEditor::InitFieldsBoxes");
1415 for(fb : fieldsBoxes)
1423 for(fb : fieldsBoxes)
1426 //NotifyInitFields(master, this);
1431 DebugLn("TableEditor::EditNew");
1433 modifiedDocument = false;
1438 DebugLn("TableEditor::EditSave");
1439 internalModifications = true;
1440 for(fb : fieldsBoxes)
1443 if(idField && list && listFields && listFields.count)
1445 DataRow listRow = list.currentRow;
1446 // ADDED THIS HERE FOR SQLITE TO REFRESH
1447 editRow.Find(idField, middle, nil, listRow.tag);
1448 SetListRowFields(editRow, listRow, false);
1449 list.Sort(listSortField, listSortOrder);
1451 internalModifications = false;
1453 for(te : tableEditors)
1456 modifiedDocument = false;
1462 Id selId = selectedId;
1463 DebugLn("TableEditor::EditLoad");
1467 internalModifications = true;
1473 lu.row = { lu.findIndex ? lu.findIndex : lu.findField.table };
1474 if(lu.valueField && eClass_IsDerived(lu.valueField.type, class(Id)) &&
1475 lu.findField && eClass_IsDerived(lu.findField.type, class(Id)))
1478 editRow.GetData(lu.valueField, id);
1479 lu.row.Find(lu.findField, middle, nil, id);
1483 for(fb : fieldsBoxes)
1485 OnCreateDynamicLookupEditors();
1486 internalModifications = false;
1487 size = size; // This seems to be required to fix autoSize on entering order screen
1489 DebugLn(" TODO: implement virtual method TableEditor::OnSubEditorsLoad");
1491 modifiedDocument = false;
1497 DebugLn("TableEditor::EditClear");
1499 internalModifications = true;
1500 for(fb : fieldsBoxes)
1502 for(e : dynamicLookupTableEditors)
1504 for(e : tableEditors)
1506 tableEditors.Free();
1507 dynamicLookupTableEditors.Free();
1508 //dynamicLookupTableEditors.size = 0;
1509 internalModifications = false;
1511 DebugLn(" TODO: remove all sub table editors");
1513 modifiedDocument = false;
1517 void SetListRowFields(Row dbRow, DataRow listRow, bool restoreSelection)
1519 // DebugLn("TableEditor::SetListRowFields");
1520 for(lf : listFields)
1522 if(lf.dataField && lf.field)
1524 if(eClass_IsDerived(lf.field.type, class(char*)))
1527 dbRow.GetData(lf.field, s);
1528 listRow.SetData(lf.dataField, s);
1531 else if(eClass_IsDerived(lf.field.type, class(Id)))
1537 dbRow.GetData(lf.field, id);
1538 s = lf.CustomLookup(id);
1539 listRow.SetData(lf.dataField, s);
1542 else if(lf.lookupFindField && (lf.lookupFindIndex || lf.lookupFindField.table) && lf.lookupValueField &&
1543 eClass_IsDerived(lf.lookupFindField.type, class(Id)) &&
1544 eClass_IsDerived(lf.lookupValueField.type, class(char*)))
1548 Row lookupRow { lf.lookupFindIndex ? lf.lookupFindIndex : lf.lookupFindField.table };
1549 dbRow.GetData(lf.field, id);
1550 if(lookupRow.Find(lf.lookupFindField, middle, nil, id))
1551 lookupRow.GetData(lf.lookupValueField, s);
1552 listRow.SetData(lf.dataField, s);
1557 else if(lf.CustomLookup && lf.field.type)
1559 char * n = lf.field.name;
1562 Class type = lf.field.type;
1563 if(type.type == unitClass && !type.typeSize)
1565 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1569 if(type.type == structClass)
1570 data = (int64)new0 byte[type.structSize];
1571 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1572 s = lf.CustomLookup((int)data);
1573 listRow.SetData(lf.dataField, s);
1574 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1575 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1576 if(type.type == structClass)
1580 else if(lf.field.type && eClass_IsDerived(lf.dataField.dataType, class(char*)))
1582 char * n = lf.field.name;
1583 char tempString[MAX_F_STRING];
1585 Class type = lf.field.type;
1587 if(type.type == unitClass && !type.typeSize)
1589 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1593 if(type.type == structClass)
1594 data = (int64)new0 byte[type.structSize];
1595 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1596 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1597 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)lf.field.type._vTbl[__ecereVMethodID_class_OnGetString])(lf.field.type, &data, tempString, null, null);
1599 s = ((char *(*)(void *, void *, char *, void *, bool *))(void *)lf.field.type._vTbl[__ecereVMethodID_class_OnGetString])(lf.field.type, (void*)data, tempString, null, null);
1601 listRow.SetData(lf.dataField, s);
1603 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1604 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1605 if(type.type == structClass)
1608 else if(lf.field.type)
1610 char * n = lf.field.name;
1611 //char tempString[256];
1613 Class type = lf.field.type;
1614 if(type.type == unitClass && !type.typeSize)
1616 Class dataType = eSystem_FindClass(type.module, type.dataTypeString);
1620 if(type.type == structClass)
1621 data = (int64)new0 byte[type.structSize];
1622 ((bool (*)())(void *)dbRow.GetData)(dbRow, lf.field, type, (type.type == structClass) ? (void *)data : &data);
1623 if(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass)
1624 listRow.SetData(lf.dataField, (void *)&data);
1626 listRow.SetData(lf.dataField, (void *)data);
1627 if(!(type.type == systemClass || type.type == unitClass || type.type == bitClass || type.type == enumClass))
1628 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, (void *)data);
1629 if(type.type == structClass)
1634 if(restoreSelection && !list.currentRow)
1637 if((select = list.FindRow(selectedId)))
1638 SelectListRow(select);
1642 Array<Id> SearchWordList()
1644 DebugLn("TableEditor::SearchWordList");
1645 #ifdef FULL_STRING_SEARCH
1651 WordEntry entries[256];
1652 Array<Id> results = null;
1653 if(searchTables && searchTables.count && searchString && searchString[0])
1655 char * searchCopy = CopyString(searchString);
1656 numTokens = TokenizeWith(searchCopy, sizeof(words) / sizeof(char *), words, " ',/-;[]{}", false);
1657 for(c = 0; c<numTokens; c++)
1659 len[c] = strlen(words[c]);
1661 entries[c] = (WordEntry)wordTree.FindString(words[c]);
1671 Map<Id, int> matches { };
1672 Map<Id, int> uniques { };
1673 MapNode<Id, int> mn;
1675 for(c = 0; c<numTokens; c++)
1677 if(entries[c] && entries[c].items && entries[c].items.count)
1679 for(i = 0; i<entries[c].items.count; i++)
1681 int count = uniques[entries[c].items.ids[i]];
1686 matches[entries[c].items.ids[i]]++;
1691 for(mn = matches.root.minimum; mn; mn = mn.next)
1694 results.Add(mn.key);
1700 else if(numTokens == 1)
1703 if(entries[0] && entries[0].items && entries[0].items.count)
1705 for(c = 0; c<entries[0].items.count; c++)
1706 results.Add(entries[0].items.ids[c]);
1718 // find a way to not load a tree for different searchFields
1719 // if the code that sets the searchFields has changed
1720 // store a search index signature containing following:
1721 // tables name, idField name and type, fields name and type
1722 void PrepareWordList()
1724 DebugLn("TableEditor::PrepareWordList");
1725 #ifdef FULL_STRING_SEARCH
1727 char filePath[MAX_FILENAME];
1730 sprintf(filePath, "%s.search", table.name);
1731 f = filePath ? FileOpenBuffered(filePath, read) : null;
1738 for(a = 0; a<26; a++)
1742 word[0] = 'a' + (char)a;
1745 letters[a] = (WordEntry)wordTree.FindString(word);
1746 for(b = 0; b<26; b++)
1748 word[1] = 'a' + (char)b;
1749 doubleLetters[a][b] = (WordEntry)wordTree.FindString(word);
1753 else if(searchTables && searchTables.count)
1758 for(a = 0; a<26; a++)
1762 word[0] = 'a' + (char)a;
1765 wordTree.Add((BTNode)(letters[a] = WordEntry { string = CopyString(word) }));
1766 for(b = 0; b<26; b++)
1768 word[1] = 'a' + (char)b;
1769 wordTree.Add((BTNode)(doubleLetters[a][b] = WordEntry { string = CopyString(word) }));
1774 wordListPrepTimer.tablesIndex = 0;
1775 wordListPrepTimer.Start();
1781 void ProcessWordListString(char * string, StringSearchIndexingMethod method, Id id)
1790 char asciiWord[1024];
1792 for(c = 0; ; c += nb)
1794 ch = UTF8GetChar(string + c, &nb);
1796 if(!ch || CharMatchCategories(ch, separators) ||
1797 (count && CharMatchCategories(ch, letters|numbers|marks|connector) != CharMatchCategories(lastCh, letters|numbers|marks|connector)))
1802 asciiWord[numChars] = 0;
1806 AddWord(word, count, method == allSubstrings, id);
1807 if(count > numChars)
1808 AddWord(asciiWord, strlen(asciiWord), method == allSubstrings, id);
1812 if(!CharMatchCategories(ch, separators))
1815 for(cc = 0; cc < nb; cc++)
1816 word[count++] = string[c + cc];
1818 asciiWord[numChars++] = ToASCII(ch);
1826 for(cc = 0; cc < nb; cc++)
1827 word[count++] = string[c + cc];
1829 asciiWord[numChars++] = ToASCII(ch);
1835 /*static */WordEntryBinaryTree wordTree
1837 CompareKey = (void *)BinaryTree::CompareString;
1838 FreeKey = BinaryTree::FreeString;
1841 WordEntry letters[26];
1842 WordEntry doubleLetters[26][26];
1844 void AddWord(char * word, int count, bool addAllSubstrings, Id id)
1846 //DebugLn("TableEditor::AddWord");
1847 #ifdef FULL_STRING_SEARCH
1849 if(addAllSubstrings)
1852 WordEntry mainEntry = null;
1853 WordEntry sEntry = null;
1855 for(s = 0; s < count; s += UTF8_NUM_BYTES(word[s]))
1860 WordEntry lEntry = null;
1861 memcpy(subWord, word + s, count-s);
1862 subWord[count-s] = 0; // THIS IS REQUIRED!! THE WHILE LOOP BELOW CHECKED count-s FIRST!!
1865 for(l = count-s; l>0; l--)
1868 WordEntry start = null, wordEntry;
1870 while(l > 0 && !UTF8_IS_FIRST(subWord[l])) l--;
1875 if(ch1 >= 'a' && ch1 <= 'z')
1877 char ch2 = subWord[1];
1878 if(count - s > 1 && ch2 >= 'a' && ch2 <= 'z')
1880 char ch2 = subWord[1];
1881 start = doubleLetters[ch1 - 'a'][ch2 - 'a'];
1885 start = letters[ch1 - 'a'];
1892 while(start && (max = (WordEntry)((BTNode)start).maximum))
1894 if(strcmp(max.string, subWord) >= 0)
1896 start = start.parent;
1901 start = (WordEntry)wordTree.root;
1903 if((wordEntry = (WordEntry)((BTNode)start).FindString(subWord)))
1909 wordTree.Add((BTNode)(wordEntry = WordEntry { string = CopyString(subWord) }));
1913 mainEntry = wordEntry;
1921 if(!wordEntry.words) wordEntry.words = IdList { };
1922 wordEntry.words.Add((Id)mainEntry);
1927 if(!wordEntry.words) wordEntry.words = IdList { };
1928 wordEntry.words.Add((Id)sEntry);
1932 if(!wordEntry.words) wordEntry.words = IdList { };
1933 wordEntry.words.Add((Id)lEntry);
1935 if(!wordEntry.items) wordEntry.items = IdList { };
1936 wordEntry.items.Add(id);
1942 WordEntry wordEntry;
1943 if(!(wordEntry = (WordEntry)(wordTree.root).FindString(word)))
1944 wordTree.Add((BTNode)(wordEntry = WordEntry { string = CopyString(word) }));
1945 if(!wordEntry.items) wordEntry.items = IdList { };
1946 wordEntry.items.Add(id);
1953 public class ListField : struct
1957 DataField dataField;
1958 Field lookupFindField;
1959 Field lookupValueField;
1960 Table lookupFindIndex;
1961 String (*CustomLookup)(Id);
1986 public class LookupEditor : struct
1989 subclass(TableEditor) editorClass;
1990 Window parentWindow;
1991 Field lookupValueField;
1992 Field lookupFindField;
1993 Field lookupIdField;
1994 Table lookupFindIndex;
1997 // all methods currently perform ascii conversion and all that jazz on every string added to the index
1998 public enum StringSearchIndexingMethod { fullString, allSubstrings };
2000 public class StringSearchField
2004 StringSearchIndexingMethod method;
2006 Field lookupFindField;
2007 Field lookupValueField;
2009 //String (*CustomRead)(Id);
2012 public class StringSearchTable
2017 Array<StringSearchField> searchFields;
2020 ~StringSearchTable()
2022 delete searchFields;
2026 public class SQLiteSearchField
2030 //StringSearchIndexingMethod method;
2033 public class SQLiteSearchTable
2038 Array<SQLiteSearchField> searchFields;
2041 ~SQLiteSearchTable()
2043 delete searchFields;
2047 static WordEntry * btnodes;
2049 struct WordEntryBinaryTree : BinaryTree
2051 WordEntry * entries;
2053 void OnSerialize(IOChannel channel)
2057 uint count = this.count;
2058 DebugLn("WordEntryBinaryTree::OnSerialize");
2059 for(id = 1, node = (WordEntry)root; node;)
2066 else if(node.parent)
2068 bool isLeft = node == node.parent.left;
2073 if(isLeft && node.right)
2079 isLeft = node == node.parent.left;
2088 channel.Serialize(id);
2089 channel.Serialize((WordEntry)root);
2092 void OnUnserialize(IOChannel channel)
2094 WordEntry root, node;
2096 DebugLn("WordEntryBinaryTree::OnUnserialize");
2097 channel.Unserialize(count);
2098 entries = new WordEntry[count];
2100 channel.Unserialize(root);
2101 this.root = (BTNode)root;
2102 // count = root ? this.root.count : 0;
2104 for(node = (WordEntry)root; node;)
2109 for(c = 0; c<node.words.count; c++)
2110 node.words.ids[c] = (Id)btnodes[node.words.ids[c] - 1];
2116 else if(node.parent)
2118 bool isLeft = node == node.parent.left;
2123 if(isLeft && node.right)
2129 isLeft = node == node.parent.left;
2141 class WordEntry : struct
2145 WordEntry left, right;
2158 void OnSerialize(IOChannel channel)
2160 #ifdef FULL_STRING_SEARCH
2163 channel.Serialize(id);
2164 channel.Serialize(string);
2165 channel.Serialize(items);
2170 channel.Serialize(words.count);
2171 for(c = 0; c < words.count; c++)
2173 uint id = ((WordEntry)words.ids[c]).id;
2174 channel.Serialize(id);
2180 channel.Serialize(none);
2183 // channel.Serialize(words);
2184 channel.Serialize(left);
2185 channel.Serialize(right);
2190 channel.Serialize(nothing);
2195 void OnUnserialize(IOChannel channel)
2197 #ifdef FULL_STRING_SEARCH
2199 channel.Unserialize(id);
2204 // TODO: Fix typed_object issues
2205 entry = btnodes[id - 1] = eInstance_New(class(WordEntry));
2206 this = (void *)entry;
2208 channel.Unserialize(string);
2209 channel.Unserialize(items);
2210 channel.Unserialize(words);
2212 channel.Unserialize(left);
2213 if(left) { left.parent = (void *)this; }
2214 channel.Unserialize(right);
2215 if(right) { right.parent = (void *)this; }
2217 // TODO: Precomp errors without extra brackets
2218 depth = ((BTNode)((void *)this)).depthProp;
2227 class ListEnumerationTimer : Timer
2235 Map<Id, int> uniques;
2238 class WordListPrepTimer : Timer
2246 class EnumerateThread : Thread
2265 bool abort, abortNow;
2272 //if(app.ProcessInput(true))
2275 Row r { editor.table };
2279 if(editor.listFields && editor.idField)
2281 /*for(c=0; c<matches.count && !abort; c++)
2283 if(r.Find(editor.idField, middle, nil, matches[c]))
2288 row = editor.list.AddRow();
2289 r.GetData(editor.idField, id);
2291 editor.SetListRowFields(r, row, true);
2295 DebugLn($"WordList match cannot be found in database.");
2298 else if(editor.idField && editor.stringField)
2300 /*for(c=0; c<matches.count && !abort; c++)
2302 if(r.Find(editor.idField, middle, nil, matches[c]))
2306 r.GetData(editor.idField, id);
2307 r.GetData(editor.stringField, s);
2309 editor.list.AddString(s).tag = id;
2314 DebugLn($"WordList match cannot be found in database.");
2320 else if(!editor.disabledFullListing)
2322 if(editor.listFields && editor.idField)
2325 while(r.Next() && !abort)
2330 r.GetData(editor.idField, id);
2331 //if(app.ProcessInput(true))
2335 row = editor.list.AddRow();
2337 editor.SetListRowFields(r, row, true);
2342 else if(editor.idField && editor.stringField)
2344 /*while(r.Next() && !abort)
2349 r.GetData(editor.idField, id);
2350 r.GetData(editor.stringField, s);
2351 editor.list.AddString(s).tag = id;
2363 editor.list.Sort(editor.listSortField, editor.listSortOrder);
2382 editor.list.Update(null);
2383 //app.Wait(); // Sleep(0.2f);
2384 //if(app.ProcessInput(true))
2387 //app.UpdateDisplay();
2394 static define app = ((GuiApplication)__thisModule);
2396 static inline void DebugLn(typed_object object, ...)
2398 #if defined(_DEBUG_LINE)
2401 va_start(args, object);
2402 PrintStdArgsToBuffer(buffer, sizeof(buffer), object, args);