]> git.saurik.com Git - wxWidgets.git/blame - samples/db/dbtest.cpp
New file use to configure the compilation on VMS-systems
[wxWidgets.git] / samples / db / dbtest.cpp
CommitLineData
108106cf
JS
1///////////////////////////////////////////////////////////////////////////////
2// Name: dbtest.cpp
3// Purpose: wxWindows database demo app
4// Author: George Tasker
5// Modified by:
6// Created: 1998
7// RCS-ID: $Id$
8// Copyright: (c) 1998 Remstar International, Inc.
9// Licence: wxWindows licence
10///////////////////////////////////////////////////////////////////////////////
11
12/*
13 * SYNOPSIS START
14
15 This sample program demonstrates the cross-platform ODBC database classes
16 donated by the development team at Remstar International.
17
18 The table this sample is based on is developer contact table, and shows
19 some of the simple uses of the database classes wxDB and wxTable.
20
21
22
23 * SYNOPSIS END
24 */
25
26#ifdef __GNUG__
27#pragma implementation "dbtest.h"
28#endif
29
30#include "wx/wxprec.h"
31
32#ifdef __BORLANDC__
33#pragma hdrstop
34#endif //__BORLANDC__
35
36#ifndef WX_PRECOMP
37#include <wx/wx.h>
38#endif //WX_PRECOMP
39
7e616b10
RR
40#ifdef __WXGTK__
41#include "db.xpm"
42#endif
43
5b077d48 44#include <stdio.h> /* Included strictly for reading the text file with the database parameters */
108106cf 45
5b077d48
RR
46#include <wx/db.h> /* Required in the file which will get the data source connection */
47#include <wx/dbtable.h> /* Has the wxTable object from which all data objects will inherit their data table functionality */
108106cf 48
5b077d48 49extern DbList* WXDLLEXPORT PtrBegDbList; /* from db.cpp, used in getting back error results from db connections */
108106cf 50
5b077d48
RR
51#include "dbtest.h" /* Header file for this demonstration program */
52#include "listdb.h" /* Code to support the "Lookup" button on the editor dialog */
1fc5dd6f
JS
53
54IMPLEMENT_APP(DatabaseDemoApp)
55
5b077d48
RR
56extern char ListDB_Selection[]; /* Used to return the first column value for the selected line from the listDB routines */
57extern char ListDB_Selection2[]; /* Used to return the second column value for the selected line from the listDB routines */
108106cf 58
5b077d48 59DatabaseDemoFrame *DemoFrame; /* Pointer to the main frame */
108106cf 60
108106cf
JS
61/* Pointer to the main database connection used in the program. This
62 * pointer would normally be used for doing things as database lookups
63 * for user login names and passwords, getting workstation settings, etc.
64 * ---> IMPORTANT <---
65 *
66 * For each database object created which uses this wxDB pointer
67 * connection to the database, when a CommitTrans() or RollBackTrans()
68 * will commit or rollback EVERY object which uses this wxDB pointer.
69 *
70 * To allow each table object (those derived from wxTable) to be
71 * individually committed or rolled back, you MUST use a different
72 * instance of wxDB in the constructor of the table. Doing so creates
73 * more overhead, and will use more database connections (some DBs have
74 * connection limits...), so use connections sparringly.
75 *
76 * It is recommended that one "main" database connection be created for
77 * the entire program to use for READ-ONLY database accesses, but for each
78 * table object which will do a CommitTrans() or RollbackTrans() that a
79 * new wxDB object be created and used for it.
80 */
5b077d48 81
108106cf
JS
82wxDB *READONLY_DB;
83
108106cf
JS
84/*
85 * This function will return the exact string(s) from the database engine
86 * indicating all error conditions which have just occured during the
87 * last call to the database engine.
88 *
89 * This demo uses the returned string by displaying it in a wxMessageBox. The
1fc5dd6f 90 * formatting therefore is not the greatest, but this is just a demo, not a
108106cf
JS
91 * finished product. :-) gt
92 *
93 * NOTE: The value returned by this function is for temporary use only and
94 * should be copied for long term use
95 */
96char *GetExtendedDBErrorMsg(char *ErrFile, int ErrLine)
97{
98 static wxString msg;
99
100 wxString tStr;
101
102 if (ErrFile || ErrLine)
103 {
5b077d48 104 msg += "File: ";
108106cf
JS
105 msg += ErrFile;
106 msg += " Line: ";
1fc5dd6f 107 tStr.Printf("%d",ErrLine);
108106cf
JS
108 msg += tStr.GetData();
109 msg += "\n";
110 }
111
5b077d48 112 msg.Append ("\nODBC errors:\n");
108106cf 113 msg += "\n";
5b077d48
RR
114
115 /* Scan through each database connection displaying
116 * any ODBC errors that have occured. */
108106cf
JS
117 for (DbList *pDbList = PtrBegDbList; pDbList; pDbList = pDbList->PtrNext)
118 {
119 // Skip over any free connections
120 if (pDbList->Free)
121 continue;
122 // Display errors for this connection
123 for (int i = 0; i < DB_MAX_ERROR_HISTORY; i++)
124 {
125 if (pDbList->PtrDb->errorList[i])
126 {
127 msg.Append(pDbList->PtrDb->errorList[i]);
128 if (strcmp(pDbList->PtrDb->errorList[i],"") != 0)
129 msg.Append("\n");
130 }
131 }
132 }
133 msg += "\n";
134
1fc5dd6f 135 return (char*) (const char*) msg;
108106cf
JS
136} // GetExtendedDBErrorMsg
137
138
1fc5dd6f 139bool DatabaseDemoApp::OnInit()
108106cf
JS
140{
141 // Create the main frame window
1fc5dd6f 142 DemoFrame = new DatabaseDemoFrame(NULL, "wxWindows Database Demo", wxPoint(50, 50), wxSize(537, 480));
108106cf
JS
143
144 // Give it an icon
1fc5dd6f 145 DemoFrame->SetIcon(wxICON(db));
108106cf
JS
146
147 // Make a menubar
148 wxMenu *file_menu = new wxMenu;
149 file_menu->Append(FILE_CREATE, "&Create contact table");
150 file_menu->Append(FILE_EXIT, "E&xit");
151
152 wxMenu *edit_menu = new wxMenu;
153 edit_menu->Append(EDIT_PARAMETERS, "&Parameters...");
154
155 wxMenu *about_menu = new wxMenu;
156 about_menu->Append(ABOUT_DEMO, "&About");
157
158 wxMenuBar *menu_bar = new wxMenuBar;
159 menu_bar->Append(file_menu, "&File");
160 menu_bar->Append(edit_menu, "&Edit");
161 menu_bar->Append(about_menu, "&About");
162 DemoFrame->SetMenuBar(menu_bar);
163
164 // Initialize the ODBC Environment for Database Operations
165 if (SQLAllocEnv(&DbConnectInf.Henv) != SQL_SUCCESS)
166 {
167 wxMessageBox("A problem occured while trying to get a connection to the data source","DB CONNECTION ERROR",wxOK | wxICON_EXCLAMATION);
168 return NULL;
169 }
170
a968c19a
GT
171 params.ODBCSource[0] = 0;
172 params.UserName[0] = 0;
173 params.Password[0] = 0;
174 params.DirPath[0] = 0;
175
108106cf
JS
176 FILE *paramFile;
177 if ((paramFile = fopen(paramFilename, "r")) == NULL)
178 {
179 wxString tStr;
1fc5dd6f
JS
180 tStr.Printf("Unable to open the parameter file '%s' for reading.\n\nYou must specify the data source, user name, and\npassword that will be used and save those settings.",paramFilename);
181 wxMessageBox(tStr,"File I/O Error...",wxOK | wxICON_EXCLAMATION);
a968c19a 182
108106cf
JS
183 DemoFrame->BuildParameterDialog(NULL);
184 if ((paramFile = fopen(paramFilename, "r")) == NULL)
185 return FALSE;
186 }
187
188 char buffer[1000+1];
189 fgets(buffer, sizeof(params.ODBCSource), paramFile);
190 buffer[strlen(buffer)-1] = '\0';
191 strcpy(params.ODBCSource,buffer);
192
193 fgets(buffer, sizeof(params.UserName), paramFile);
194 buffer[strlen(buffer)-1] = '\0';
195 strcpy(params.UserName,buffer);
196
197 fgets(buffer, sizeof(params.Password), paramFile);
198 buffer[strlen(buffer)-1] = '\0';
199 strcpy(params.Password,buffer);
200
65d7ddc4
GT
201 fgets(buffer, sizeof(params.DirPath), paramFile);
202 buffer[strlen(buffer)-1] = '\0';
203 strcpy(params.DirPath,buffer);
204
108106cf
JS
205 fclose(paramFile);
206
207 // Connect to datasource
208 strcpy(DbConnectInf.Dsn, params.ODBCSource); // ODBC data source name (created with ODBC Administrator under Win95/NT)
209 strcpy(DbConnectInf.Uid, params.UserName); // database username - must already exist in the data source
210 strcpy(DbConnectInf.AuthStr, params.Password); // password database username
65d7ddc4
GT
211 strcpy(DbConnectInf.defaultDir,params.DirPath); // path where the table exists (needed for dBase)
212
108106cf
JS
213 READONLY_DB = GetDbConnection(&DbConnectInf);
214 if (READONLY_DB == 0)
215 {
216 wxMessageBox("Unable to connect to the data source.\n\nCheck the name of your data source to verify it has been correctly entered/spelled.\n\nWith some databases, the user name and password must\nbe created with full rights to the CONTACT table prior to making a connection\n(using tools provided by the database manufacturer)", "DB CONNECTION ERROR...",wxOK | wxICON_EXCLAMATION);
217 DemoFrame->BuildParameterDialog(NULL);
218 strcpy(DbConnectInf.Dsn, "");
219 strcpy(DbConnectInf.Uid, "");
220 strcpy(DbConnectInf.AuthStr, "");
221 wxMessageBox("Now exiting program.\n\nRestart program to try any new settings.","Notice...",wxOK | wxICON_INFORMATION);
222 return(FALSE);
223 }
224
225 DemoFrame->BuildEditorDialog();
226
227 // Show the frame
228 DemoFrame->Show(TRUE);
229
1fc5dd6f 230 return TRUE;
108106cf
JS
231} // DatabaseDemoApp::OnInit()
232
1fc5dd6f
JS
233BEGIN_EVENT_TABLE(DatabaseDemoFrame, wxFrame)
234 EVT_MENU(FILE_CREATE, DatabaseDemoFrame::OnCreate)
235 EVT_MENU(FILE_EXIT, DatabaseDemoFrame::OnExit)
236 EVT_MENU(EDIT_PARAMETERS, DatabaseDemoFrame::OnEditParameters)
237 EVT_MENU(ABOUT_DEMO, DatabaseDemoFrame::OnAbout)
238 EVT_CLOSE(DatabaseDemoFrame::OnCloseWindow)
239END_EVENT_TABLE()
108106cf
JS
240
241// DatabaseDemoFrame constructor
1fc5dd6f
JS
242DatabaseDemoFrame::DatabaseDemoFrame(wxFrame *frame, const wxString& title,
243 const wxPoint& pos, const wxSize& size):
244 wxFrame(frame, -1, title, pos, size)
108106cf
JS
245{
246// Put any code in necessary for initializing the main frame here
247}
248
1fc5dd6f
JS
249void DatabaseDemoFrame::OnCreate(wxCommandEvent& event)
250{
251 CreateDataTable();
252}
108106cf 253
1fc5dd6f 254void DatabaseDemoFrame::OnExit(wxCommandEvent& event)
108106cf 255{
1fc5dd6f
JS
256 this->Destroy();
257}
108106cf 258
1fc5dd6f
JS
259void DatabaseDemoFrame::OnEditParameters(wxCommandEvent& event)
260{
261 if ((pEditorDlg->mode != mCreate) && (pEditorDlg->mode != mEdit))
262 BuildParameterDialog(this);
263 else
264 wxMessageBox("Cannot change database parameters while creating or editing a record","Notice...",wxOK | wxICON_INFORMATION);
265}
266
267void DatabaseDemoFrame::OnAbout(wxCommandEvent& event)
268{
269 wxMessageBox("wxWindows sample program for database classes\n\nContributed on 27 July 1998","About...",wxOK | wxICON_INFORMATION);
270}
108106cf 271
1fc5dd6f 272void DatabaseDemoFrame::OnCloseWindow(wxCloseEvent& event)
108106cf
JS
273{
274 // Put any additional checking necessary to make certain it is alright
275 // to close the program here that is not done elsewhere
276
1fc5dd6f 277 this->Destroy();
e3065973 278} // DatabaseDemoFrame::OnCloseWindow()
108106cf
JS
279
280
281void DatabaseDemoFrame::CreateDataTable()
282{
1fc5dd6f 283 bool Ok = (wxMessageBox("Any data currently residing in the table will be erased.\n\nAre you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
284
285 if (!Ok)
286 return;
287
288 wxBeginBusyCursor();
289
1fc5dd6f 290 bool success = TRUE;
108106cf
JS
291
292 Ccontact *Contact = new Ccontact();
293 if (!Contact)
294 {
295 wxEndBusyCursor();
296 wxMessageBox("Error allocating memory for 'Ccontact'object.\n\nTable was not created.","Error...",wxOK | wxICON_EXCLAMATION);
297 return;
298 }
299
65d7ddc4 300 if (!Contact->CreateTable(FALSE))
108106cf
JS
301 {
302 wxEndBusyCursor();
303 wxString tStr;
304 tStr = "Error creating CONTACTS table.\nTable was not created.\n\n";
305 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 306 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
307 success = FALSE;
308 }
309 else
310 {
311 if (!Contact->CreateIndexes())
312 {
313 wxEndBusyCursor();
314 wxString tStr;
315 tStr = "Error creating CONTACTS indexes.\nIndexes will be unavailable.\n\n";
316 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 317 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
318 success = FALSE;
319 }
320 }
65d7ddc4
GT
321 while (wxIsBusy())
322 wxEndBusyCursor();
108106cf
JS
323
324 delete Contact;
325
326 if (success)
327 wxMessageBox("Table and index(es) were successfully created.","Notice...",wxOK | wxICON_INFORMATION);
328} // DatabaseDemoFrame::CreateDataTable()
329
330
331void DatabaseDemoFrame::BuildEditorDialog()
332{
333 pEditorDlg = new CeditorDlg(this);
334 if (!pEditorDlg)
335 wxMessageBox("Unable to create the editor dialog for some reason","Error...",wxOK | wxICON_EXCLAMATION);
336} // DatabaseDemoFrame::BuildEditorDialog()
337
338
339void DatabaseDemoFrame::BuildParameterDialog(wxWindow *parent)
340{
341 pParamDlg = new CparameterDlg(parent);
342
343 if (!pParamDlg)
344 wxMessageBox("Unable to create the parameter dialog for some reason","Error...",wxOK | wxICON_EXCLAMATION);
345} // DatabaseDemoFrame::BuildParameterDialog()
346
347
348/*
349 * Constructor note: If no wxDB object is passed in, a new connection to the database
350 * is created for this instance of Ccontact. This can be a slow process depending
351 * on the database engine being used, and some database engines have a limit on the
352 * number of connections (either hard limits, or license restricted) so care should
353 * be used to use as few connections as is necessary.
354 * IMPORTANT: Objects which share a wxDB pointer are ALL acted upon whenever a member
355 * function of pDb is called (i.e. CommitTrans() or RollbackTrans(), so if modifying
356 * or creating a table objects which use the same pDb, know that all the objects
357 * will be committed or rolled back when any of the objects has this function call made.
358 */
65d7ddc4 359Ccontact::Ccontact (wxDB *pwxDB) : wxTable(pwxDB ? pwxDB : GetDbConnection(&DbConnectInf),CONTACT_TABLE_NAME,CONTACT_NO_COLS,NULL,!QUERY_ONLY,DbConnectInf.defaultDir)
108106cf
JS
360{
361 // This is used to represent whether the database connection should be released
362 // when this instance of the object is deleted. If using the same connection
363 // for multiple instance of database objects, then the connection should only be
364 // released when the last database instance using the connection is deleted
365 freeDbConn = !pwxDB;
366
367 SetupColumns();
368
369} // Ccontact Constructor
370
371
372void Ccontact::Initialize()
373{
374 Name[0] = 0;
375 Addr1[0] = 0;
376 Addr2[0] = 0;
377 City[0] = 0;
378 State[0] = 0;
379 PostalCode[0] = 0;
380 Country[0] = 0;
381 JoinDate.year = 1980;
382 JoinDate.month = 1;
383 JoinDate.day = 1;
384 JoinDate.hour = 0;
385 JoinDate.minute = 0;
386 JoinDate.second = 0;
387 JoinDate.fraction = 0;
388 NativeLanguage = langENGLISH;
389 IsDeveloper = FALSE;
390 Contributions = 0;
391 LinesOfCode = 0L;
392} // Ccontact::Initialize
393
394
395Ccontact::~Ccontact()
396{
397 if (freeDbConn)
398 {
399 if (!FreeDbConnection(pDb))
400 {
401 wxString tStr;
402 tStr = "Unable to Free the Ccontact data table handle\n\n";
403 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 404 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
405 }
406 }
407} // Ccontract destructor
408
409
410/*
411 * Handles setting up all the connections for the interface from the wxTable
412 * functions to interface to the data structure used to store records in
413 * memory, and for all the column definitions that define the table structure
414 */
415void Ccontact::SetupColumns()
416{
65d7ddc4
GT
417 // NOTE: Columns now are 8 character names, as that is all dBase can support. Longer
418 // names can be used for other database engines
419 SetColDefs ( 0,"NAME", DB_DATA_TYPE_VARCHAR, Name, SQL_C_CHAR, sizeof(Name), TRUE, TRUE); // Primary index
420 SetColDefs ( 1,"ADDRESS1", DB_DATA_TYPE_VARCHAR, Addr1, SQL_C_CHAR, sizeof(Addr1), FALSE,TRUE);
421 SetColDefs ( 2,"ADDRESS2", DB_DATA_TYPE_VARCHAR, Addr2, SQL_C_CHAR, sizeof(Addr2), FALSE,TRUE);
422 SetColDefs ( 3,"CITY", DB_DATA_TYPE_VARCHAR, City, SQL_C_CHAR, sizeof(City), FALSE,TRUE);
423 SetColDefs ( 4,"STATE", DB_DATA_TYPE_VARCHAR, State, SQL_C_CHAR, sizeof(State), FALSE,TRUE);
424 SetColDefs ( 5,"POSTCODE", DB_DATA_TYPE_VARCHAR, PostalCode, SQL_C_CHAR, sizeof(PostalCode), FALSE,TRUE);
425 SetColDefs ( 6,"COUNTRY", DB_DATA_TYPE_VARCHAR, Country, SQL_C_CHAR, sizeof(Country), FALSE,TRUE);
426 SetColDefs ( 7,"JOINDATE", DB_DATA_TYPE_DATE, &JoinDate, SQL_C_TIMESTAMP, sizeof(JoinDate), FALSE,TRUE);
427 SetColDefs ( 8,"IS_DEV", DB_DATA_TYPE_INTEGER, &IsDeveloper, SQL_C_BOOLEAN(IsDeveloper),sizeof(IsDeveloper), FALSE,TRUE);
428 SetColDefs ( 9,"CONTRIBS", DB_DATA_TYPE_INTEGER, &Contributions, SQL_C_USHORT, sizeof(Contributions), FALSE,TRUE);
429 SetColDefs (10,"LINE_CNT", DB_DATA_TYPE_INTEGER, &LinesOfCode, SQL_C_ULONG, sizeof(LinesOfCode), FALSE,TRUE);
430 SetColDefs (11,"LANGUAGE", DB_DATA_TYPE_INTEGER, &NativeLanguage, SQL_C_ENUM, sizeof(NativeLanguage), FALSE,TRUE);
108106cf
JS
431} // Ccontact::SetupColumns
432
433
1fc5dd6f 434bool Ccontact::CreateIndexes(void)
108106cf
JS
435{
436 // This index could easily be accomplished with an "orderBy" clause,
437 // but is done to show how to construct a non-primary index.
438 wxString indexName;
439 CidxDef idxDef[2];
440
1fc5dd6f 441 bool Ok = TRUE;
108106cf 442
8493118f 443 strcpy(idxDef[0].ColName, "IS_DEV");
108106cf
JS
444 idxDef[0].Ascending = TRUE;
445
446 strcpy(idxDef[1].ColName, "NAME");
447 idxDef[1].Ascending = TRUE;
448
449 indexName = CONTACT_TABLE_NAME;
450 indexName += "_IDX1";
1fc5dd6f 451 Ok = CreateIndex((char*) (const char*) indexName, TRUE, 2, idxDef);
108106cf
JS
452
453 return Ok;
454} // Ccontact::CreateIndexes()
455
456
457/*
458 * Having a function to do a query on the primary key (and possibly others) is
459 * very efficient and tighter coding so that it is available where ever the object
460 * is. Great for use with multiple tables when not using views or outer joins
461 */
1fc5dd6f 462bool Ccontact::FetchByName(char *name)
108106cf 463{
1fc5dd6f
JS
464 whereStr.Printf("NAME = '%s'",name);
465 where = (char*) (const char*) this->whereStr;
108106cf
JS
466 orderBy = 0;
467
468 if (!Query())
469 return(FALSE);
470
471 // Fetch the record
472 return(GetNext());
473
474} // Ccontact::FetchByName()
475
476
477/*
478 *
479 * ************* DIALOGS ***************
480 *
481 */
482
483
484/* CeditorDlg constructor
485 *
486 * Creates the dialog used for creating/editing/deleting/copying a Ccontact object.
487 * This dialog actually is drawn in the main frame of the program
488 *
489 * An instance of Ccontact is created - "Contact" - which is used to hold the Ccontact
490 * object that is currently being worked with.
491 */
f6fcbb63
RR
492
493BEGIN_EVENT_TABLE(CeditorDlg, wxPanel)
494 EVT_BUTTON(-1, CeditorDlg::OnButton)
495END_EVENT_TABLE()
496
108106cf
JS
497CeditorDlg::CeditorDlg(wxWindow *parent) : wxPanel (parent, 1, 1, 460, 455)
498{
499 // Since the ::OnCommand() function is overridden, this prevents the widget
500 // detection in ::OnCommand() until all widgets have been initialized to prevent
501 // uninitialized pointers from crashing the program
502 widgetPtrsSet = FALSE;
503
504 // Create the data structure and a new database connection.
65d7ddc4 505 // (As there is not a pDb being passed in the constructor, a new database
108106cf
JS
506 // connection is created)
507 Contact = new Ccontact();
508
509 if (!Contact)
510 {
511 wxMessageBox("Unable to instantiate an instance of Ccontact","Error...",wxOK | wxICON_EXCLAMATION);
512 return;
513 }
514
515 // Check if the table exists or not. If it doesn't, ask the user if they want to
516 // create the table. Continue trying to create the table until it exists, or user aborts
65d7ddc4 517 while (!Contact->pDb->TableExists((char *)CONTACT_TABLE_NAME,DbConnectInf.Uid,DbConnectInf.defaultDir))
108106cf
JS
518 {
519 wxString tStr;
1fc5dd6f 520 tStr.Printf("Unable to open the table '%s'.\n\nTable may need to be created...?\n\n",CONTACT_TABLE_NAME);
108106cf 521 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 522 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf 523
1fc5dd6f 524 bool createTable = (wxMessageBox("Do you wish to try to create/clear the CONTACTS table?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
525
526 if (!createTable)
527 {
528 delete Contact;
529 Close();
530 DemoFrame->Close();
531 return;
532 }
533 else
534 DemoFrame->CreateDataTable();
535 }
536
537 // Tables must be "opened" before anything other than creating/deleting table can be done
538 if (!Contact->Open())
539 {
540 // Table does exist, there was some problem opening it. Currently this should
541 // never fail, except in the case of the table not exisiting. Open() basically
542 // only sets up variable/pointer values, other than checking for table existence.
543 if (Contact->pDb->TableExists((char *)CONTACT_TABLE_NAME))
544 {
545 wxString tStr;
1fc5dd6f 546 tStr.Printf("Unable to open the table '%s'.\n\n",CONTACT_TABLE_NAME);
108106cf 547 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 548 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
549 delete Contact;
550 Close();
551 DemoFrame->Close();
552 return;
553 }
554 }
555
556 // Build the dialog
108106cf 557
5b077d48
RR
558 (void)new wxStaticBox(this, EDITOR_DIALOG_FN_GROUP, "", wxPoint(15, 1), wxSize(497, 69), 0, "FunctionGrp");
559 (void)new wxStaticBox(this, EDITOR_DIALOG_SEARCH_GROUP, "", wxPoint(417, 1), wxSize(95, 242), 0, "SearchGrp");
108106cf 560
1fc5dd6f
JS
561 pCreateBtn = new wxButton(this, EDITOR_DIALOG_CREATE, "&Create", wxPoint(25, 21), wxSize(70, 35), 0, wxDefaultValidator, "CreateBtn");
562 pEditBtn = new wxButton(this, EDITOR_DIALOG_EDIT, "&Edit", wxPoint(102, 21), wxSize(70, 35), 0, wxDefaultValidator, "EditBtn");
563 pDeleteBtn = new wxButton(this, EDITOR_DIALOG_DELETE, "&Delete", wxPoint(179, 21), wxSize(70, 35), 0, wxDefaultValidator, "DeleteBtn");
564 pCopyBtn = new wxButton(this, EDITOR_DIALOG_COPY, "Cop&y", wxPoint(256, 21), wxSize(70, 35), 0, wxDefaultValidator, "CopyBtn");
565 pSaveBtn = new wxButton(this, EDITOR_DIALOG_SAVE, "&Save", wxPoint(333, 21), wxSize(70, 35), 0, wxDefaultValidator, "SaveBtn");
566 pCancelBtn = new wxButton(this, EDITOR_DIALOG_CANCEL, "C&ancel", wxPoint(430, 21), wxSize(70, 35), 0, wxDefaultValidator, "CancelBtn");
108106cf 567
1fc5dd6f
JS
568 pPrevBtn = new wxButton(this, EDITOR_DIALOG_PREV, "<< &Prev", wxPoint(430, 81), wxSize(70, 35), 0, wxDefaultValidator, "PrevBtn");
569 pNextBtn = new wxButton(this, EDITOR_DIALOG_NEXT, "&Next >>", wxPoint(430, 121), wxSize(70, 35), 0, wxDefaultValidator, "NextBtn");
570 pQueryBtn = new wxButton(this, EDITOR_DIALOG_QUERY, "&Query", wxPoint(430, 161), wxSize(70, 35), 0, wxDefaultValidator, "QueryBtn");
571 pResetBtn = new wxButton(this, EDITOR_DIALOG_RESET, "&Reset", wxPoint(430, 200), wxSize(70, 35), 0, wxDefaultValidator, "ResetBtn");
108106cf 572
1fc5dd6f
JS
573 pNameMsg = new wxStaticText(this, EDITOR_DIALOG_NAME_MSG, "Name:", wxPoint(17, 80), wxSize(-1, -1), 0, "NameMsg");
574 pNameTxt = new wxTextCtrl(this, EDITOR_DIALOG_NAME_TEXT, "", wxPoint(17, 97), wxSize(308, 25), 0, wxDefaultValidator, "NameTxt");
575 pNameListBtn = new wxButton(this, EDITOR_DIALOG_LOOKUP, "&Lookup", wxPoint(333, 99), wxSize(70, 24), 0, wxDefaultValidator, "LookupBtn");
108106cf 576
1fc5dd6f
JS
577 pAddress1Msg = new wxStaticText(this, EDITOR_DIALOG_ADDRESS1_MSG, "Address:", wxPoint(17, 130), wxSize(-1, -1), 0, "Address1Msg");
578 pAddress1Txt = new wxTextCtrl(this, EDITOR_DIALOG_ADDRESS2_TEXT, "", wxPoint(17, 147), wxSize(308, 25), 0, wxDefaultValidator, "Address1Txt");
108106cf 579
1fc5dd6f
JS
580 pAddress2Msg = new wxStaticText(this, EDITOR_DIALOG_ADDRESS2_MSG, "Address:", wxPoint(17, 180), wxSize(-1, -1), 0, "Address2Msg");
581 pAddress2Txt = new wxTextCtrl(this, EDITOR_DIALOG_ADDRESS2_TEXT, "", wxPoint(17, 197), wxSize(308, 25), 0, wxDefaultValidator, "Address2Txt");
108106cf 582
1fc5dd6f
JS
583 pCityMsg = new wxStaticText(this, EDITOR_DIALOG_CITY_MSG, "City:", wxPoint(17, 230), wxSize(-1, -1), 0, "CityMsg");
584 pCityTxt = new wxTextCtrl(this, EDITOR_DIALOG_CITY_TEXT, "", wxPoint(17, 247), wxSize(225, 25), 0, wxDefaultValidator, "CityTxt");
108106cf 585
1fc5dd6f
JS
586 pStateMsg = new wxStaticText(this, EDITOR_DIALOG_STATE_MSG, "State:", wxPoint(250, 230), wxSize(-1, -1), 0, "StateMsg");
587 pStateTxt = new wxTextCtrl(this, EDITOR_DIALOG_STATE_TEXT, "", wxPoint(250, 247), wxSize(153, 25), 0, wxDefaultValidator, "StateTxt");
108106cf 588
1fc5dd6f
JS
589 pCountryMsg = new wxStaticText(this, EDITOR_DIALOG_COUNTRY_MSG, "Country:", wxPoint(17, 280), wxSize(-1, -1), 0, "CountryMsg");
590 pCountryTxt = new wxTextCtrl(this, EDITOR_DIALOG_COUNTRY_TEXT, "", wxPoint(17, 297), wxSize(225, 25), 0, wxDefaultValidator, "CountryTxt");
108106cf 591
1fc5dd6f
JS
592 pPostalCodeMsg = new wxStaticText(this, EDITOR_DIALOG_POSTAL_MSG, "Postal Code:", wxPoint(250, 280), wxSize(-1, -1), 0, "PostalCodeMsg");
593 pPostalCodeTxt = new wxTextCtrl(this, EDITOR_DIALOG_POSTAL_TEXT, "", wxPoint(250, 297), wxSize(153, 25), 0, wxDefaultValidator, "PostalCodeTxt");
108106cf 594
1fc5dd6f 595 wxString choice_strings[5];
108106cf
JS
596 choice_strings[0] = "English";
597 choice_strings[1] = "French";
598 choice_strings[2] = "German";
599 choice_strings[3] = "Spanish";
600 choice_strings[4] = "Other";
1fc5dd6f
JS
601 pNativeLangChoice = new wxChoice(this, EDITOR_DIALOG_LANG_CHOICE, wxPoint(17, 346), wxSize(277, -1), 5, choice_strings);
602 pNativeLangMsg = new wxStaticText(this, EDITOR_DIALOG_LANG_MSG, "Native language:", wxPoint(17, 330), wxSize(-1, -1), 0, "NativeLangMsg");
108106cf 603
1fc5dd6f 604 wxString radio_strings[2];
108106cf
JS
605 radio_strings[0] = "No";
606 radio_strings[1] = "Yes";
1fc5dd6f 607 pDeveloperRadio = new wxRadioBox(this,EDITOR_DIALOG_DEVELOPER,"Developer:",wxPoint(303,330),wxSize(-1,-1),2,radio_strings,2,wxHORIZONTAL);
108106cf 608
1fc5dd6f
JS
609 pJoinDateMsg = new wxStaticText(this, EDITOR_DIALOG_JOIN_MSG, "Date joined:", wxPoint(17, 380), wxSize(-1, -1), 0, "JoinDateMsg");
610 pJoinDateTxt = new wxTextCtrl(this, EDITOR_DIALOG_JOIN_TEXT, "", wxPoint(17, 397), wxSize(150, 25), 0, wxDefaultValidator, "JoinDateTxt");
108106cf 611
1fc5dd6f
JS
612 pContribMsg = new wxStaticText(this, EDITOR_DIALOG_CONTRIB_MSG, "Contributions:", wxPoint(175, 380), wxSize(-1, -1), 0, "ContribMsg");
613 pContribTxt = new wxTextCtrl(this, EDITOR_DIALOG_CONTRIB_TEXT, "", wxPoint(175, 397), wxSize(120, 25), 0, wxDefaultValidator, "ContribTxt");
108106cf 614
1fc5dd6f
JS
615 pLinesMsg = new wxStaticText(this, EDITOR_DIALOG_LINES_MSG, "Lines of code:", wxPoint(303, 380), wxSize(-1, -1), 0, "LinesMsg");
616 pLinesTxt = new wxTextCtrl(this, EDITOR_DIALOG_LINES_TEXT, "", wxPoint(303, 397), wxSize(100, 25), 0, wxDefaultValidator, "LinesTxt");
108106cf
JS
617
618 // Now that all the widgets on the panel are created, its safe to allow ::OnCommand() to
619 // handle all widget processing
620 widgetPtrsSet = TRUE;
621
622 // Setup the orderBy and where clauses to return back a single record as the result set,
623 // as there will only be one record being shown on the dialog at a time, this optimizes
624 // network traffic by only returning a one row result
625
626 Contact->orderBy = "NAME"; // field name to sort by
627
628 // The wxString "whereStr" is not a member of the wxTable object, it is a member variable
629 // specifically in the Ccontact class. It is used here for simpler construction of a varying
630 // length string, and then after the string is built, the wxTable member variable "where" is
631 // assigned the pointer to the constructed string.
632 //
633 // The constructed where clause below has a sub-query within it "SELECT MIN(NAME) FROM %s"
634 // to achieve a single row (in this case the first name in alphabetical order).
f6fcbb63 635
8fc613f1 636 // commented out because PostgreSQL can't do this
65d7ddc4
GT
637 if (Contact->pDb->Dbms() != dbmsPOSTGRES)
638 {
639 Contact->whereStr.sprintf("NAME = (SELECT MIN(NAME) FROM %s)",Contact->tableName);
640 // NOTE: (const char*) returns a pointer which may not be valid later, so this is short term use only
641 Contact->where = (char*) (const char*) Contact->whereStr;
642 }
643 else
644 Contact->where = 0;
108106cf
JS
645
646 // Perform the Query to get the result set.
647 // NOTE: If there are no rows returned, that is a valid result, so Query() would return TRUE.
648 // Only if there is a database error will Query() come back as FALSE
649 if (!Contact->Query())
650 {
651 wxString tStr;
652 tStr = "ODBC error during Query()\n\n";
653 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 654 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
655 GetParent()->Close();
656 return;
657 }
658
659 // Since Query succeeded, now get the row that was returned
660 if (!Contact->GetNext())
661 // If the GetNext() failed at this point, then there are no rows to retrieve,
662 // so clear the values in the members of "Contact" so that PutData() blanks the
663 // widgets on the dialog
664 Contact->Initialize();
665
666 SetMode(mView);
667 PutData();
668
669 Show(TRUE);
670} // CeditorDlg constructor
671
672
e3065973 673void CeditorDlg::OnCloseWindow(wxCloseEvent& event)
108106cf
JS
674{
675 // Clean up time
676 if ((mode != mCreate) && (mode != mEdit))
677 {
678 if (Contact)
679 delete Contact;
e3065973 680 this->Destroy();
108106cf
JS
681 }
682 else
683 {
684 wxMessageBox("Must finish processing the current record being created/modified before exiting","Notice...",wxOK | wxICON_INFORMATION);
e3065973 685 event.Veto();
108106cf 686 }
e3065973 687} // CeditorDlg::OnCloseWindow()
108106cf
JS
688
689
f6fcbb63
RR
690void CeditorDlg::OnButton( wxCommandEvent &event )
691{
692 wxWindow *win = (wxWindow*) event.GetEventObject();
693 OnCommand( *win, event );
694}
695
65d7ddc4 696
108106cf
JS
697void CeditorDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
698{
699 wxString widgetName;
700
701 widgetName = win.GetName();
702
703 if (!widgetPtrsSet)
704 return;
705
706 if (widgetName == pCreateBtn->GetName())
707 {
708 Contact->Initialize();
709 PutData();
710 SetMode( mCreate );
711 pNameTxt->SetValue("");
712 pNameTxt->SetFocus();
713 return;
714 }
715
716 if (widgetName == pEditBtn->GetName())
717 {
718 saveName = Contact->Name;
719 SetMode( mEdit );
720 pNameTxt->SetFocus();
721 return;
722 }
723
724 if (widgetName == pCopyBtn->GetName())
725 {
726 SetMode(mCreate);
727 pNameTxt->SetValue("");
728 pNameTxt->SetFocus();
729 return;
730 }
731
732 if (widgetName == pDeleteBtn->GetName())
733 {
1fc5dd6f 734 bool Ok = (wxMessageBox("Are you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
735
736 if (!Ok)
737 return;
738
739 if (Ok && Contact->Delete())
740 {
741 // NOTE: Deletions are not finalized until a CommitTrans() is performed.
742 // If the commit were not performed, the program will continue to
743 // show the table contents as if they were deleted until this instance
744 // of Ccontact is deleted. If the Commit wasn't performed, the
745 // database will automatically Rollback the changes when the database
746 // connection is terminated
747 Contact->pDb->CommitTrans();
748
749 // Try to get the row that followed the just deleted row in the orderBy sequence
750 if (!GetNextRec())
751 {
752 // There was now row (in sequence) after the just deleted row, so get the
753 // row which preceded the just deleted row
754 if (!GetPrevRec())
755 {
756 // There are now no rows remaining, so clear the dialog widgets
757 Contact->Initialize();
758 PutData();
759 }
760 }
761 SetMode(mode); // force reset of button enable/disable
762 }
763 else
764 // Delete failed
765 Contact->pDb->RollbackTrans();
766
767 SetMode(mView);
768 return;
769 }
770
771 if (widgetName == pSaveBtn->GetName())
772 {
773 Save();
774 return;
775 }
776
777 if (widgetName == pCancelBtn->GetName())
778 {
1fc5dd6f 779 bool Ok = (wxMessageBox("Are you sure?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
780
781 if (!Ok)
782 return;
783
1fc5dd6f 784 if (!strcmp((const char*) saveName,""))
108106cf
JS
785 {
786 Contact->Initialize();
787 PutData();
788 SetMode(mView);
789 return;
790 }
791 else
792 {
793 // Requery previous record
1fc5dd6f 794 if (Contact->FetchByName((char*) (const char*) saveName))
108106cf
JS
795 {
796 PutData();
797 SetMode(mView);
798 return;
799 }
800 }
801
802 // Previous record not available, retrieve first record in table
65d7ddc4
GT
803 if (Contact->pDb->Dbms() != dbmsPOSTGRES)
804 {
805 // PostgreSQL can't do this
806 Contact->whereStr = "NAME = (SELECT MIN(NAME) FROM ";
807 Contact->whereStr += Contact->tableName;
808 Contact->whereStr += ")";
809 Contact->where = (char*) (const char*) Contact->whereStr;
810 }
811 else
812 Contact->where = 0;
813
108106cf
JS
814 if (!Contact->Query())
815 {
816 wxString tStr;
817 tStr = "ODBC error during Query()\n\n";
818 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 819 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
820 SetMode(mView);
821 return;
822 }
823 if (Contact->GetNext()) // Successfully read first record
824 {
825 PutData();
826 SetMode(mView);
827 return;
828 }
829 // No contacts are available, clear dialog
830 Contact->Initialize();
831 PutData();
832 SetMode(mView);
833 return;
834 } // Cancel Button
835
836 if (widgetName == pPrevBtn->GetName())
837 {
838 if (!GetPrevRec())
839 wxBell();
840 return;
841 } // Prev Button
842
843 if (widgetName == pNextBtn->GetName())
844 {
845 if (!GetNextRec())
846 wxBell();
847 return;
848 } // Next Button
849
850 if (widgetName == pQueryBtn->GetName())
851 {
852 // Display the query dialog box
853 char qryWhere[DB_MAX_WHERE_CLAUSE_LEN+1];
1fc5dd6f 854 strcpy(qryWhere, (const char*) Contact->qryWhereStr);
108106cf
JS
855 char *tblName[] = {(char *)CONTACT_TABLE_NAME, 0};
856 new CqueryDlg(GetParent(), Contact->pDb, tblName, qryWhere);
857
858 // Query the first record in the new record set and
859 // display it, if the query string has changed.
1fc5dd6f 860 if (strcmp(qryWhere, (const char*) Contact->qryWhereStr))
108106cf 861 {
8fc613f1
RR
862
863 // commented out because PostgreSQL can't do this
864 Contact->whereStr = "";
865 //Contact->orderBy = "NAME";
866 //Contact->whereStr = "NAME = (SELECT MIN(NAME) FROM ";
867 //Contact->whereStr += CONTACT_TABLE_NAME;
0d2a2b60 868
108106cf
JS
869 // Append the query where string (if there is one)
870 Contact->qryWhereStr = qryWhere;
871 if (strlen(qryWhere))
872 {
873 Contact->whereStr += " WHERE ";
874 Contact->whereStr += Contact->qryWhereStr;
875 }
876 // Close the expression with a right paren
8fc613f1 877 // Contact->whereStr += ")";
108106cf 878 // Requery the table
1fc5dd6f 879 Contact->where = (char*) (const char*) Contact->whereStr;
108106cf
JS
880 if (!Contact->Query())
881 {
882 wxString tStr;
883 tStr = "ODBC error during Query()\n\n";
884 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 885 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
886 return;
887 }
888 // Display the first record from the query set
889 if (!Contact->GetNext())
890 Contact->Initialize();
891 PutData();
892 }
893
894 // Enable/Disable the reset button
1fc5dd6f 895 pResetBtn->Enable(!Contact->qryWhereStr.IsEmpty());
108106cf
JS
896
897 return;
898 } // Query button
899
900
901 if (widgetName == pResetBtn->GetName())
902 {
903 // Clear the additional where criteria established by the query feature
904 Contact->qryWhereStr = "";
905
906 // Query the first record in the table
907 Contact->orderBy = "NAME";
0d2a2b60 908
8fc613f1
RR
909 // commented out because PostgreSQL can't do this
910 //Contact->whereStr = "NAME = (SELECT MIN(NAME) FROM ";
911 //Contact->whereStr += CONTACT_TABLE_NAME;
912 //Contact->whereStr += ")";
0d2a2b60 913
1fc5dd6f 914 Contact->where = (char*) (const char*) Contact->whereStr;
108106cf
JS
915 if (!Contact->Query())
916 {
917 wxString tStr;
918 tStr = "ODBC error during Query()\n\n";
919 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 920 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
921 return;
922 }
923 if (!Contact->GetNext())
924 Contact->Initialize();
925 PutData();
926 pResetBtn->Enable(FALSE);
927
928 return;
929 } // Reset button
930
931
932 if (widgetName == pNameListBtn->GetName())
933 {
934 new ClookUpDlg(/* wxWindow *parent */ this,
935 /* char *windowTitle */ "Select contact name",
936 /* char *tableName */ (char *) CONTACT_TABLE_NAME,
937 /* char *dispCol1 */ "NAME",
65d7ddc4 938 /* char *dispCol2 */ "JOINDATE",
108106cf
JS
939 /* char *where */ "",
940 /* char *orderBy */ "NAME",
1fc5dd6f 941 /* bool distinctValues */ TRUE);
108106cf
JS
942
943 if (ListDB_Selection && strlen(ListDB_Selection))
944 {
945 wxString w = "NAME = '";
946 w += ListDB_Selection;
947 w += "'";
1fc5dd6f 948 GetRec((char*) (const char*) w);
108106cf
JS
949 }
950
951 return;
952 }
953
954} // CeditorDlg::OnCommand()
955
956
957void CeditorDlg::FieldsEditable()
958{
959 pNameTxt->Enable((mode == mCreate) || (mode == mEdit));
960 pAddress1Txt->Enable((mode == mCreate) || (mode == mEdit));
961 pAddress2Txt->Enable((mode == mCreate) || (mode == mEdit));
962 pCityTxt->Enable((mode == mCreate) || (mode == mEdit));
963 pStateTxt->Enable((mode == mCreate) || (mode == mEdit));
964 pPostalCodeTxt->Enable((mode == mCreate) || (mode == mEdit));
965 pCountryTxt->Enable((mode == mCreate) || (mode == mEdit));
966
967 pJoinDateTxt->Enable((mode == mCreate) || (mode == mEdit));
968 pContribTxt->Enable((mode == mCreate) || (mode == mEdit));
969 pLinesTxt->Enable((mode == mCreate) || (mode == mEdit));
970 pNativeLangChoice->Enable((mode == mCreate) || (mode == mEdit));
971 pDeveloperRadio->Enable((mode == mCreate) || (mode == mEdit));
972
973} // CeditorDlg::FieldsEditable()
974
975
976void CeditorDlg::SetMode(enum DialogModes m)
977{
1fc5dd6f 978 bool edit = FALSE;
108106cf
JS
979
980 mode = m;
981 switch (mode)
982 {
983 case mCreate:
984 case mEdit:
985 edit = TRUE;
986 break;
987 case mView:
988 case mSearch:
989 edit = FALSE;
990 break;
991 default:
992 break;
993 };
994
995 if (widgetPtrsSet)
996 {
997 pCreateBtn->Enable( !edit );
998 pEditBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
999 pDeleteBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
1000 pCopyBtn->Enable( !edit && (strcmp(Contact->Name,"")!=0) );
1001 pSaveBtn->Enable( edit );
1002 pCancelBtn->Enable( edit );
1003 pPrevBtn->Enable( !edit );
1004 pNextBtn->Enable( !edit );
1005 pQueryBtn->Enable( !edit );
1fc5dd6f 1006 pResetBtn->Enable( !edit && !Contact->qryWhereStr.IsEmpty() );
108106cf
JS
1007 pNameListBtn->Enable( !edit );
1008 }
1009
1010 FieldsEditable();
1011} // CeditorDlg::SetMode()
1012
1013
1fc5dd6f 1014bool CeditorDlg::PutData()
108106cf
JS
1015{
1016 wxString tStr;
1017
1018 pNameTxt->SetValue(Contact->Name);
1019 pAddress1Txt->SetValue(Contact->Addr1);
1020 pAddress2Txt->SetValue(Contact->Addr2);
1021 pCityTxt->SetValue(Contact->City);
1022 pStateTxt->SetValue(Contact->State);
1023 pCountryTxt->SetValue(Contact->Country);
1024 pPostalCodeTxt->SetValue(Contact->PostalCode);
1025
1fc5dd6f
JS
1026 tStr.Printf("%d/%d/%d",Contact->JoinDate.month,Contact->JoinDate.day,Contact->JoinDate.year);
1027 pJoinDateTxt->SetValue(tStr);
108106cf 1028
1fc5dd6f
JS
1029 tStr.Printf("%d",Contact->Contributions);
1030 pContribTxt->SetValue(tStr);
108106cf 1031
1fc5dd6f
JS
1032 tStr.Printf("%lu",Contact->LinesOfCode);
1033 pLinesTxt->SetValue(tStr);
108106cf
JS
1034
1035 pNativeLangChoice->SetSelection(Contact->NativeLanguage);
1036
1037 pDeveloperRadio->SetSelection(Contact->IsDeveloper);
1038
1039 return TRUE;
1040} // Ceditor::PutData()
1041
1042
1043/*
1044 * Reads the data out of all the widgets on the dialog. Some data evaluation is done
1045 * to ensure that there is a name entered and that the date field is valid.
1046 *
1047 * A return value of TRUE means that valid data was retrieved from the dialog, otherwise
1048 * invalid data was found (and a message was displayed telling the user what to fix), and
1049 * the data was not placed into the appropraite fields of Ccontact
1050 */
1fc5dd6f 1051bool CeditorDlg::GetData()
108106cf
JS
1052{
1053 // Validate that the data currently entered into the widgets is valid data
1054
1055 wxString tStr;
1056 tStr = pNameTxt->GetValue();
1fc5dd6f 1057 if (!strcmp((const char*) tStr,""))
108106cf
JS
1058 {
1059 wxMessageBox("A name is required for entry into the contact table","Notice...",wxOK | wxICON_INFORMATION);
1060 return FALSE;
1061 }
1062
1fc5dd6f 1063 bool invalid = FALSE;
108106cf
JS
1064 int mm,dd,yyyy;
1065 int first, second;
1066
1067 tStr = pJoinDateTxt->GetValue();
1068 if (tStr.Freq('/') != 2)
1069 invalid = TRUE;
1070
1071 // Find the month, day, and year tokens
1072 if (!invalid)
1073 {
1074 first = tStr.First('/');
1075 second = tStr.Last('/');
1076
1077 mm = atoi(tStr.SubString(0,first));
1078 dd = atoi(tStr.SubString(first+1,second));
1079 yyyy = atoi(tStr.SubString(second+1,tStr.Length()-1));
1080
1081 invalid = !(mm && dd && yyyy);
1082 }
1083
1084 // Force Year 2000 compliance
1085 if (!invalid && (yyyy < 1000))
1086 invalid = TRUE;
1087
1088 // Check the token ranges for validity
1089 if (!invalid)
1090 {
1091 if (yyyy > 9999)
1092 invalid = TRUE;
1093 else if ((mm < 1) || (mm > 12))
1094 invalid = TRUE;
1095 else
1096 {
1097 if (dd < 1)
1098 invalid = TRUE;
1099 else
1100 {
1101 int days[12] = {31,28,31,30,31,30,
1102 31,31,30,31,30,31};
1103 if (dd > days[mm-1])
1104 {
1105 invalid = TRUE;
1106 if ((dd == 29) && (mm == 2))
1107 {
1108 if (((yyyy % 4) == 0) && (((yyyy % 100) != 0) || ((yyyy % 400) == 0)))
1109 invalid = FALSE;
1110 }
1111 }
1112 }
1113 }
1114 }
1115
1116 if (!invalid)
1117 {
1118 Contact->JoinDate.month = mm;
1119 Contact->JoinDate.day = dd;
1120 Contact->JoinDate.year = yyyy;
1121 }
1122 else
1123 {
1124 wxMessageBox("Improper date format. Please check the date\nspecified and try again.\n\nNOTE: Dates are in american format (MM/DD/YYYY)","Notice...",wxOK | wxICON_INFORMATION);
1125 return FALSE;
1126 }
1127
1128 tStr = pNameTxt->GetValue();
1fc5dd6f 1129 strcpy(Contact->Name,(const char*) tStr);
108106cf
JS
1130 strcpy(Contact->Addr1,pAddress1Txt->GetValue());
1131 strcpy(Contact->Addr2,pAddress2Txt->GetValue());
1132 strcpy(Contact->City,pCityTxt->GetValue());
1133 strcpy(Contact->State,pStateTxt->GetValue());
1134 strcpy(Contact->Country,pCountryTxt->GetValue());
1135 strcpy(Contact->PostalCode,pPostalCodeTxt->GetValue());
1136
1137 Contact->Contributions = atoi(pContribTxt->GetValue());
1138 Contact->LinesOfCode = atol(pLinesTxt->GetValue());
1139
1140 Contact->NativeLanguage = (enum Language) pNativeLangChoice->GetSelection();
65d7ddc4 1141 Contact->IsDeveloper = pDeveloperRadio->GetSelection() > 0;
108106cf
JS
1142
1143 return TRUE;
1144} // CeditorDlg::GetData()
1145
1146
1147/*
1148 * Retrieve data from the dialog, verify the validity of the data, and if it is valid,
1149 * try to insert/update the data to the table based on the current 'mode' the dialog
1150 * is set to.
1151 *
1152 * A return value of TRUE means the insert/update was completed successfully, a return
1153 * value of FALSE means that Save() failed. If returning FALSE, then this function
1154 * has displayed a detailed error message for the user.
1155 */
1fc5dd6f 1156bool CeditorDlg::Save()
108106cf 1157{
1fc5dd6f 1158 bool failed = FALSE;
108106cf
JS
1159
1160 // Read the data in the widgets of the dialog to get the user's data
1161 if (!GetData())
1162 failed = TRUE;
1163
1164 // Perform any other required validations necessary before saving
108106cf
JS
1165 if (!failed)
1166 {
1167 wxBeginBusyCursor();
1168
1169 if (mode == mCreate)
1170 {
1171 RETCODE result = Contact->Insert();
1172
1173 failed = (result != DB_SUCCESS);
1174 if (failed)
1175 {
1176 // Some errors may be expected, like a duplicate key, so handle those instances with
1177 // specific error messages.
1178 if (result == DB_ERR_INTEGRITY_CONSTRAINT_VIOL)
1179 {
1180 wxString tStr;
1181 tStr = "A duplicate key value already exists in the table.\nUnable to save record\n\n";
1182 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 1183 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1184 }
1185 else
1186 {
1187 // Some other unexpexted error occurred
1188 wxString tStr;
1189 tStr = "Database insert failed\n\n";
1190 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 1191 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1192 }
1193 }
1194 }
1195 else // mode == mEdit
1196 {
1197 if (!Contact->Update())
1198 {
1199 wxString tStr;
1200 tStr = "Database update failed\n\n";
1201 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 1202 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1203 failed = TRUE;
1204 }
1205 }
1206
1207 if (!failed)
1208 {
1209 Contact->pDb->CommitTrans();
1210 SetMode(mView); // Sets the dialog mode back to viewing after save is successful
1211 }
1212 else
1213 Contact->pDb->RollbackTrans();
1214
1215 wxEndBusyCursor();
1216 }
1217
1218 return !failed;
1219} // CeditorDlg::Save()
1220
1221
1222/*
1223 * Where this program is only showing a single row at a time in the dialog,
1224 * a special where clause must be built to find just the single row which,
1225 * in sequence, would follow the currently displayed row.
1226 */
1fc5dd6f 1227bool CeditorDlg::GetNextRec()
108106cf
JS
1228{
1229 wxString w;
1230
65d7ddc4
GT
1231 if (Contact->pDb->Dbms() != dbmsPOSTGRES)
1232 {
1233 // PostgreSQL can't do this
1234 w = "NAME = (SELECT MIN(NAME) FROM ";
1235 w += Contact->tableName;
1236 w += " WHERE NAME > '";
1237 }
1238 else
1239 w = "(NAME > '";
1240
108106cf
JS
1241 w += Contact->Name;
1242 w += "'";
1243
1244 // If a query where string is currently set, append that criteria
1fc5dd6f 1245 if (!Contact->qryWhereStr.IsEmpty())
108106cf
JS
1246 {
1247 w += " AND (";
1248 w += Contact->qryWhereStr;
1249 w += ")";
1250 }
1251
1252 w += ")";
1fc5dd6f 1253 return(GetRec((char*) (const char*) w));
108106cf
JS
1254
1255} // CeditorDlg::GetNextRec()
1256
1257
1258/*
1259 * Where this program is only showing a single row at a time in the dialog,
1260 * a special where clause must be built to find just the single row which,
1261 * in sequence, would precede the currently displayed row.
1262 */
1fc5dd6f 1263bool CeditorDlg::GetPrevRec()
108106cf
JS
1264{
1265 wxString w;
1266
65d7ddc4
GT
1267 if (Contact->pDb->Dbms() != dbmsPOSTGRES)
1268 {
1269 // PostgreSQL can't do this
1270 w = "NAME = (SELECT MAX(NAME) FROM ";
1271 w += Contact->tableName;
1272 w += " WHERE NAME < '";
1273 }
1274 else
1275 w = "(NAME < '";
1276
108106cf
JS
1277 w += Contact->Name;
1278 w += "'";
1279
1280 // If a query where string is currently set, append that criteria
1fc5dd6f 1281 if (!Contact->qryWhereStr.IsEmpty())
108106cf
JS
1282 {
1283 w += " AND (";
1284 w += Contact->qryWhereStr;
1285 w += ")";
1286 }
1287
1288 w += ")";
1289
1fc5dd6f 1290 return(GetRec((char*) (const char*)w));
108106cf
JS
1291
1292} // CeditorDlg::GetPrevRec()
1293
1294
1295/*
1296 * This function is here to avoid duplicating this same code in both the
1297 * GetPrevRec() and GetNextRec() functions
1298 */
1fc5dd6f 1299bool CeditorDlg::GetRec(char *whereStr)
108106cf
JS
1300{
1301 Contact->where = whereStr;
1302 Contact->orderBy = "NAME";
65d7ddc4 1303
108106cf
JS
1304 if (!Contact->Query())
1305 {
1306 wxString tStr;
1307 tStr = "ODBC error during Query()\n\n";
1308 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 1309 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1310
1311 return(FALSE);
1312 }
1313
1314 if (Contact->GetNext())
1315 {
1316 PutData();
1317 return(TRUE);
1318 }
1319 else
1320 return(FALSE);
1321} // CeditorDlg::GetRec()
1322
1323
1324
1325/*
1326 * CparameterDlg constructor
1327 */
e3065973
JS
1328
1329BEGIN_EVENT_TABLE(CparameterDlg, wxDialog)
65d7ddc4
GT
1330 EVT_BUTTON(PARAMETER_DIALOG_SAVE, CparameterDlg::OnButton)
1331 EVT_BUTTON(PARAMETER_DIALOG_CANCEL, CparameterDlg::OnButton)
e3065973
JS
1332 EVT_CLOSE(CparameterDlg::OnCloseWindow)
1333END_EVENT_TABLE()
1334
65d7ddc4 1335CparameterDlg::CparameterDlg(wxWindow *parent) : wxDialog (parent, PARAMETER_DIALOG, "ODBC parameter settings", wxPoint(-1, -1), wxSize(400, 325))
108106cf
JS
1336{
1337 // Since the ::OnCommand() function is overridden, this prevents the widget
1338 // detection in ::OnCommand() until all widgets have been initialized to prevent
1339 // uninitialized pointers from crashing the program
1340 widgetPtrsSet = FALSE;
1341
1fc5dd6f
JS
1342 pParamODBCSourceMsg = new wxStaticText(this, PARAMETER_DIALOG_SOURCE_MSG, "ODBC data sources:", wxPoint(10, 10), wxSize(-1, -1), 0, "ParamODBCSourceMsg");
1343 pParamODBCSourceList = new wxListBox(this, PARAMETER_DIALOG_SOURCE_LISTBOX, wxPoint(10, 29), wxSize(285, 150), 0, 0, wxLB_SINGLE|wxLB_ALWAYS_SB, wxDefaultValidator, "ParamODBCSourceList");
108106cf 1344
1fc5dd6f
JS
1345 pParamUserNameMsg = new wxStaticText(this, PARAMETER_DIALOG_NAME_MSG, "Database user name:", wxPoint(10, 193), wxSize(-1, -1), 0, "ParamUserNameMsg");
1346 pParamUserNameTxt = new wxTextCtrl(this, PARAMETER_DIALOG_NAME_TEXT, "", wxPoint(10, 209), wxSize(140, 25), 0, wxDefaultValidator, "ParamUserNameTxt");
108106cf 1347
1fc5dd6f
JS
1348 pParamPasswordMsg = new wxStaticText(this, PARAMETER_DIALOG_PASSWORD_MSG, "Password:", wxPoint(156, 193), wxSize(-1, -1), 0, "ParamPasswordMsg");
1349 pParamPasswordTxt = new wxTextCtrl(this, PARAMETER_DIALOG_PASSWORD_TEXT, "", wxPoint(156, 209), wxSize(140, 25), 0, wxDefaultValidator, "ParamPasswordTxt");
108106cf 1350
65d7ddc4
GT
1351 pParamDirPathMsg = new wxStaticText(this, PARAMETER_DIALOG_DIRPATH_MSG, "Directory:", wxPoint(10, 243), wxSize(-1, -1), 0, "ParamDirPathMsg");
1352 pParamDirPathTxt = new wxTextCtrl(this, PARAMETER_DIALOG_DIRPATH_TEXT, "", wxPoint(10, 259), wxSize(140, 25), 0, wxDefaultValidator, "ParamDirPathTxt");
1353
1fc5dd6f
JS
1354 pParamSaveBtn = new wxButton(this, PARAMETER_DIALOG_SAVE, "&Save", wxPoint(310, 21), wxSize(70, 35), 0, wxDefaultValidator, "ParamSaveBtn");
1355 pParamCancelBtn = new wxButton(this, PARAMETER_DIALOG_CANCEL, "C&ancel", wxPoint(310, 66), wxSize(70, 35), 0, wxDefaultValidator, "ParamCancelBtn");
108106cf
JS
1356
1357 // Now that all the widgets on the panel are created, its safe to allow ::OnCommand() to
1358 // handle all widget processing
1359 widgetPtrsSet = TRUE;
1360
1361 saved = FALSE;
025e88c5 1362 savedParamSettings = wxGetApp().params;
108106cf
JS
1363
1364 Centre(wxBOTH);
1365 PutData();
1fc5dd6f 1366 ShowModal();
108106cf
JS
1367} // CparameterDlg constructor
1368
1369
e3065973 1370void CparameterDlg::OnCloseWindow(wxCloseEvent& event)
108106cf
JS
1371{
1372 // Put any additional checking necessary to make certain it is alright
1373 // to close the program here that is not done elsewhere
1374 if (!saved)
1375 {
1fc5dd6f 1376 bool Ok = (wxMessageBox("No changes have been saved.\n\nAre you sure you wish exit the parameter screen?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
1377
1378 if (!Ok)
e3065973
JS
1379 {
1380 event.Veto();
1381 return;
1382 }
108106cf 1383
025e88c5 1384 wxGetApp().params = savedParamSettings;
108106cf
JS
1385 }
1386
1387 if (GetParent() != NULL)
1388 GetParent()->SetFocus();
e3065973
JS
1389 this->Destroy();
1390
1391} // Cparameter::OnCloseWindow()
108106cf
JS
1392
1393
65d7ddc4
GT
1394void CparameterDlg::OnButton( wxCommandEvent &event )
1395{
1396 wxWindow *win = (wxWindow*) event.GetEventObject();
1397 OnCommand( *win, event );
1398}
1399
108106cf
JS
1400void CparameterDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
1401{
1402 wxString widgetName;
1403
1404 widgetName = win.GetName();
1405
1406 if (!widgetPtrsSet)
1407 return;
1408
1409 if (widgetName == pParamSaveBtn->GetName())
1410 {
1411 if (Save())
1412 {
1413 wxString tStr;
1414 tStr = "Database parameters have been saved.";
1415 if (GetParent() != NULL) // The parameter dialog was not called during startup due to a missing cfg file
1416 tStr += "\nNew parameters will take effect the next time the program is started.";
1fc5dd6f 1417 wxMessageBox(tStr,"Notice...",wxOK | wxICON_INFORMATION);
108106cf
JS
1418 saved = TRUE;
1419 Close();
1420 }
1421 return;
1422 }
1423
1424 if (widgetName == pParamCancelBtn->GetName())
1425 {
1426 Close();
1427 return;
1428 }
1429} // CparameterDlg::OnCommand()
1430
1431
1fc5dd6f 1432bool CparameterDlg::PutData()
108106cf
JS
1433{
1434 // Fill the data source list box
1435 FillDataSourceList();
1436
1437 // Fill in the fields from the params object
a968c19a
GT
1438 if (wxGetApp().params.ODBCSource && wxStrlen(wxGetApp().params.ODBCSource))
1439 pParamODBCSourceList->SetStringSelection(wxGetApp().params.ODBCSource);
025e88c5
JS
1440 pParamUserNameTxt->SetValue(wxGetApp().params.UserName);
1441 pParamPasswordTxt->SetValue(wxGetApp().params.Password);
65d7ddc4 1442 pParamDirPathTxt->SetValue(wxGetApp().params.DirPath);
108106cf
JS
1443 return TRUE;
1444} // CparameterDlg::PutData()
1445
1446
1fc5dd6f 1447bool CparameterDlg::GetData()
108106cf
JS
1448{
1449 wxString tStr;
1450 if (pParamODBCSourceList->GetStringSelection())
1451 {
1452 tStr = pParamODBCSourceList->GetStringSelection();
025e88c5 1453 if (tStr.Length() > (sizeof(wxGetApp().params.ODBCSource)-1))
108106cf
JS
1454 {
1455 wxString errmsg;
1fc5dd6f
JS
1456 errmsg.Printf("ODBC Data source name is longer than the data structure to hold it.\n'Cparameter.ODBCSource' must have a larger character array\nto handle a data source with this long of a name\n\nThe data source currently selected is %d characters long.",tStr.Length());
1457 wxMessageBox(errmsg,"Internal program error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1458 return FALSE;
1459 }
025e88c5 1460 strcpy(wxGetApp().params.ODBCSource, tStr);
108106cf
JS
1461 }
1462 else
1463 return FALSE;
1464
1465 tStr = pParamUserNameTxt->GetValue();
025e88c5 1466 if (tStr.Length() > (sizeof(wxGetApp().params.UserName)-1))
108106cf
JS
1467 {
1468 wxString errmsg;
1fc5dd6f
JS
1469 errmsg.Printf("User name is longer than the data structure to hold it.\n'Cparameter.UserName' must have a larger character array\nto handle a data source with this long of a name\n\nThe user name currently specified is %d characters long.",tStr.Length());
1470 wxMessageBox(errmsg,"Internal program error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1471 return FALSE;
1472 }
025e88c5 1473 strcpy(wxGetApp().params.UserName, tStr);
108106cf
JS
1474
1475 tStr = pParamPasswordTxt->GetValue();
025e88c5 1476 if (tStr.Length() > (sizeof(wxGetApp().params.Password)-1))
108106cf
JS
1477 {
1478 wxString errmsg;
1fc5dd6f
JS
1479 errmsg.Printf("Password is longer than the data structure to hold it.\n'Cparameter.Password' must have a larger character array\nto handle a data source with this long of a name\n\nThe password currently specified is %d characters long.",tStr.Length());
1480 wxMessageBox(errmsg,"Internal program error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1481 return FALSE;
1482 }
025e88c5 1483 strcpy(wxGetApp().params.Password,tStr);
65d7ddc4
GT
1484
1485 tStr = pParamDirPathTxt->GetValue();
1486 tStr.Replace("\\","/");
1487 if (tStr.Length() > (sizeof(wxGetApp().params.DirPath)-1))
1488 {
1489 wxString errmsg;
1490 errmsg.Printf("DirPath is longer than the data structure to hold it.\n'Cparameter.DirPath' must have a larger character array\nto handle a data source with this long of a name\n\nThe password currently specified is %d characters long.",tStr.Length());
1491 wxMessageBox(errmsg,"Internal program error...",wxOK | wxICON_EXCLAMATION);
1492 return FALSE;
1493 }
1494 strcpy(wxGetApp().params.DirPath,tStr);
108106cf
JS
1495 return TRUE;
1496} // CparameterDlg::GetData()
1497
1498
1fc5dd6f 1499bool CparameterDlg::Save()
108106cf 1500{
025e88c5 1501 Cparameters saveParams = wxGetApp().params;
108106cf
JS
1502 if (!GetData())
1503 {
025e88c5 1504 wxGetApp().params = saveParams;
108106cf
JS
1505 return FALSE;
1506 }
1507
1508 FILE *paramFile;
1509 if ((paramFile = fopen(paramFilename, "wt")) == NULL)
1510 {
1511 wxString tStr;
1fc5dd6f
JS
1512 tStr.Printf("Unable to write/overwrite '%s'.",paramFilename);
1513 wxMessageBox(tStr,"File I/O Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1514 return FALSE;
1515 }
1516
025e88c5 1517 fputs(wxGetApp().params.ODBCSource, paramFile);
108106cf 1518 fputc('\n', paramFile);
025e88c5 1519 fputs(wxGetApp().params.UserName, paramFile);
108106cf 1520 fputc('\n', paramFile);
025e88c5 1521 fputs(wxGetApp().params.Password, paramFile);
108106cf 1522 fputc('\n', paramFile);
65d7ddc4
GT
1523 fputs(wxGetApp().params.DirPath, paramFile);
1524 fputc('\n', paramFile);
108106cf
JS
1525 fclose(paramFile);
1526
1527 return TRUE;
1528} // CparameterDlg::Save()
1529
1530
1531void CparameterDlg::FillDataSourceList()
1532{
1533 char Dsn[SQL_MAX_DSN_LENGTH + 1];
1534 char DsDesc[255];
1535 wxStringList strList;
1536
1537 while(GetDataSource(DbConnectInf.Henv, Dsn, SQL_MAX_DSN_LENGTH+1, DsDesc, 255))
1538 strList.Add(Dsn);
1539
1540 strList.Sort();
1541 strList.Add("");
1542 char **p = strList.ListToArray();
1543
1544 for (int i = 0; strlen(p[i]); i++)
1545 pParamODBCSourceList->Append(p[i]);
1546} // CparameterDlg::CparameterDlg::FillDataSourceList()
1547
1548
f6fcbb63
RR
1549BEGIN_EVENT_TABLE(CqueryDlg, wxDialog)
1550 EVT_BUTTON(-1, CqueryDlg::OnButton)
e3065973 1551 EVT_CLOSE(CqueryDlg::OnCloseWindow)
f6fcbb63
RR
1552END_EVENT_TABLE()
1553
108106cf 1554// CqueryDlg() constructor
1fc5dd6f 1555CqueryDlg::CqueryDlg(wxWindow *parent, wxDB *pDb, char *tblName[], char *pWhereArg) : wxDialog (parent, QUERY_DIALOG, "Query", wxPoint(-1, -1), wxSize(480, 360))
108106cf
JS
1556{
1557 wxBeginBusyCursor();
1558
1559 colInf = 0;
1560 dbTable = 0;
1561 masterTableName = tblName[0];
1562 widgetPtrsSet = FALSE;
1563 pDB = pDb;
1564
1565 // Initialize the WHERE clause from the string passed in
1566 pWhere = pWhereArg; // Save a pointer to the output buffer
1567 if (strlen(pWhere) > DB_MAX_WHERE_CLAUSE_LEN) // Check the length of the buffer passed in
1568 {
1569 wxString s;
1fc5dd6f
JS
1570 s.Printf("Maximum where clause length exceeded.\nLength must be less than %d", DB_MAX_WHERE_CLAUSE_LEN+1);
1571 wxMessageBox(s,"Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1572 Close();
1573 return;
1574 }
1575
1fc5dd6f
JS
1576 pQueryCol1Msg = new wxStaticText(this, QUERY_DIALOG_COL_MSG, "Column 1:", wxPoint(10, 10), wxSize(69, 16), 0, "QueryCol1Msg");
1577 pQueryCol1Choice = new wxChoice(this, QUERY_DIALOG_COL_CHOICE, wxPoint(10, 27), wxSize(250, 27), 0, 0, 0, wxDefaultValidator, "QueryCol1Choice");
108106cf 1578
1fc5dd6f
JS
1579 pQueryNotMsg = new wxStaticText(this, QUERY_DIALOG_NOT_MSG, "NOT", wxPoint(268, 10), wxSize(-1, -1), 0, "QueryNotMsg");
1580 pQueryNotCheck = new wxCheckBox(this, QUERY_DIALOG_NOT_CHECKBOX, "", wxPoint(275, 37), wxSize(20, 20), 0, wxDefaultValidator, "QueryNotCheck");
108106cf 1581
1fc5dd6f 1582 wxString choice_strings[9];
108106cf
JS
1583 choice_strings[0] = "=";
1584 choice_strings[1] = "<";
1585 choice_strings[2] = ">";
1586 choice_strings[3] = "<=";
1587 choice_strings[4] = ">=";
1588 choice_strings[5] = "Begins";
1589 choice_strings[6] = "Contains";
1590 choice_strings[7] = "Like";
1591 choice_strings[8] = "Between";
1fc5dd6f
JS
1592 pQueryOperatorMsg = new wxStaticText(this, QUERY_DIALOG_OP_MSG, "Operator:", wxPoint(305, 10), wxSize(-1, -1), 0, "QueryOperatorMsg");
1593 pQueryOperatorChoice = new wxChoice(this, QUERY_DIALOG_OP_CHOICE, wxPoint(305, 27), wxSize(80, 27), 9, choice_strings, 0, wxDefaultValidator, "QueryOperatorChoice");
108106cf 1594
1fc5dd6f
JS
1595 pQueryCol2Msg = new wxStaticText(this, QUERY_DIALOG_COL2_MSG, "Column 2:", wxPoint(10, 65), wxSize(69, 16), 0, "QueryCol2Msg");
1596 pQueryCol2Choice = new wxChoice(this, QUERY_DIALOG_COL2_CHOICE, wxPoint(10, 82), wxSize(250, 27), 0, 0, 0, wxDefaultValidator, "QueryCol2Choice");
108106cf 1597
1fc5dd6f
JS
1598 pQuerySqlWhereMsg = new wxStaticText(this, QUERY_DIALOG_WHERE_MSG, "SQL where clause:", wxPoint(10, 141), wxSize(-1, -1), 0, "QuerySqlWhereMsg");
1599 pQuerySqlWhereMtxt = new wxTextCtrl(this, QUERY_DIALOG_WHERE_TEXT, "", wxPoint(10, 159), wxSize(377, 134), wxTE_MULTILINE, wxDefaultValidator, "QuerySqlWhereMtxt");
108106cf 1600
1fc5dd6f
JS
1601 pQueryAddBtn = new wxButton(this, QUERY_DIALOG_ADD, "&Add", wxPoint(406, 24), wxSize(56, 26), 0, wxDefaultValidator, "QueryAddBtn");
1602 pQueryAndBtn = new wxButton(this, QUERY_DIALOG_AND, "A&nd", wxPoint(406, 58), wxSize(56, 26), 0, wxDefaultValidator, "QueryAndBtn");
1603 pQueryOrBtn = new wxButton(this, QUERY_DIALOG_OR, "&Or", wxPoint(406, 92), wxSize(56, 26), 0, wxDefaultValidator, "QueryOrBtn");
108106cf 1604
1fc5dd6f
JS
1605 pQueryLParenBtn = new wxButton(this, QUERY_DIALOG_LPAREN, "(", wxPoint(406, 126), wxSize(26, 26), 0, wxDefaultValidator, "QueryLParenBtn");
1606 pQueryRParenBtn = new wxButton(this, QUERY_DIALOG_RPAREN, ")", wxPoint(436, 126), wxSize(26, 26), 0, wxDefaultValidator, "QueryRParenBtn");
108106cf 1607
1fc5dd6f
JS
1608 pQueryDoneBtn = new wxButton(this, QUERY_DIALOG_DONE, "&Done", wxPoint(406, 185), wxSize(56, 26), 0, wxDefaultValidator, "QueryDoneBtn");
1609 pQueryClearBtn = new wxButton(this, QUERY_DIALOG_CLEAR, "C&lear", wxPoint(406, 218), wxSize(56, 26), 0, wxDefaultValidator, "QueryClearBtn");
1610 pQueryCountBtn = new wxButton(this, QUERY_DIALOG_COUNT, "&Count", wxPoint(406, 252), wxSize(56, 26), 0, wxDefaultValidator, "QueryCountBtn");
108106cf 1611
1fc5dd6f
JS
1612 pQueryValue1Msg = new wxStaticText(this, QUERY_DIALOG_VALUE1_MSG, "Value:", wxPoint(277, 66), wxSize(-1, -1), 0, "QueryValue1Msg");
1613 pQueryValue1Txt = new wxTextCtrl(this, QUERY_DIALOG_VALUE1_TEXT, "", wxPoint(277, 83), wxSize(108, 25), 0, wxDefaultValidator, "QueryValue1Txt");
108106cf 1614
1fc5dd6f
JS
1615 pQueryValue2Msg = new wxStaticText(this, QUERY_DIALOG_VALUE2_MSG, "AND", wxPoint(238, 126), wxSize(-1, -1), 0, "QueryValue2Msg");
1616 pQueryValue2Txt = new wxTextCtrl(this, QUERY_DIALOG_VALUE2_TEXT, "", wxPoint(277, 120), wxSize(108, 25), 0, wxDefaultValidator, "QueryValue2Txt");
1617
1618 pQueryHintGrp = new wxStaticBox(this, QUERY_DIALOG_HINT_GROUP, "", wxPoint(10, 291), wxSize(377, 40), 0, "QueryHintGrp");
1619 pQueryHintMsg = new wxStaticText(this, QUERY_DIALOG_HINT_MSG, "", wxPoint(16, 306), wxSize(-1, -1), 0, "QueryHintMsg");
108106cf
JS
1620
1621 widgetPtrsSet = TRUE;
1622 // Initialize the dialog
1623 wxString qualName;
1624 pQueryCol2Choice->Append("VALUE -->");
1625 colInf = pDB->GetColumns(tblName);
65d7ddc4
GT
1626
1627 if (!colInf)
1628 {
1629 wxEndBusyCursor();
1630 wxString tStr;
1631 tStr = "ODBC error during GetColumns()\n\n";
1632 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1633 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
1634 return;
1635 }
1636
108106cf
JS
1637 for (int i = 0; colInf[i].colName && strlen(colInf[i].colName); i++)
1638 {
1639 // If there is more than one table being queried, qualify
1640 // the column names with the table name prefix.
1641 if (tblName[1] && strlen(tblName[1]))
1642 {
1fc5dd6f
JS
1643 qualName.Printf("%s.%s", colInf[i].tableName, colInf[i].colName);
1644 pQueryCol1Choice->Append(qualName);
1645 pQueryCol2Choice->Append(qualName);
108106cf
JS
1646 }
1647 else // Single table query, append just the column names
1648 {
1649 pQueryCol1Choice->Append(colInf[i].colName);
1650 pQueryCol2Choice->Append(colInf[i].colName);
1651 }
1652 }
1653
1654 pQueryCol1Choice->SetSelection(0);
1655 pQueryCol2Choice->SetSelection(0);
1656 pQueryOperatorChoice->SetSelection(0);
1657
1658 pQueryValue2Msg->Show(FALSE);
1659 pQueryValue2Txt->Show(FALSE);
1660
1661 pQueryHintMsg->SetLabel(langQRY_EQ);
1662
1663 pQuerySqlWhereMtxt->SetValue(pWhere);
1664
1665 wxEndBusyCursor();
1666
1667 // Display the dialog window
108106cf 1668 Centre(wxBOTH);
1fc5dd6f 1669 ShowModal();
108106cf
JS
1670
1671} // CqueryDlg() constructor
1672
1673
f6fcbb63
RR
1674void CqueryDlg::OnButton( wxCommandEvent &event )
1675{
1676 wxWindow *win = (wxWindow*) event.GetEventObject();
1677 OnCommand( *win, event );
1678}
1679
108106cf
JS
1680void CqueryDlg::OnCommand(wxWindow& win, wxCommandEvent& event)
1681{
1682 // Widget pointers won't be set when the dialog is constructed.
1683 // Control is passed through this function once for each widget on
1684 // a dialog as the dialog is constructed.
1685 if (!widgetPtrsSet)
1686 return;
1687
1688 wxString widgetName = win.GetName();
1689
1690 // Operator choice box
1691 if (widgetName == pQueryOperatorChoice->GetName())
1692 {
1693 // Set the help text
1694 switch((qryOp) pQueryOperatorChoice->GetSelection())
1695 {
1696 case qryOpEQ:
1697 pQueryHintMsg->SetLabel(langQRY_EQ);
1698 break;
1699 case qryOpLT:
1700 pQueryHintMsg->SetLabel(langQRY_LT);
1701 break;
1702 case qryOpGT:
1703 pQueryHintMsg->SetLabel(langQRY_GT);
1704 break;
1705 case qryOpLE:
1706 pQueryHintMsg->SetLabel(langQRY_LE);
1707 break;
1708 case qryOpGE:
1709 pQueryHintMsg->SetLabel(langQRY_GE);
1710 break;
1711 case qryOpBEGINS:
1712 pQueryHintMsg->SetLabel(langQRY_BEGINS);
1713 break;
1714 case qryOpCONTAINS:
1715 pQueryHintMsg->SetLabel(langQRY_CONTAINS);
1716 break;
1717 case qryOpLIKE:
1718 pQueryHintMsg->SetLabel(langQRY_LIKE);
1719 break;
1720 case qryOpBETWEEN:
1721 pQueryHintMsg->SetLabel(langQRY_BETWEEN);
1722 break;
1723 }
1724
1725 // Hide the value2 widget
1726 pQueryValue2Msg->Show(FALSE); // BETWEEN will show this widget
1727 pQueryValue2Txt->Show(FALSE); // BETWEEN will show this widget
1728
1729 // Disable the NOT operator for <, <=, >, >=
1730 switch((qryOp) pQueryOperatorChoice->GetSelection())
1731 {
1732 case qryOpLT:
1733 case qryOpGT:
1734 case qryOpLE:
1735 case qryOpGE:
1736 pQueryNotCheck->SetValue(0);
1737 pQueryNotCheck->Enable(FALSE);
1738 break;
1739 default:
1740 pQueryNotCheck->Enable(TRUE);
1741 break;
1742 }
1743
1744 // Manipulate the dialog to handle the selected operator
1745 switch((qryOp) pQueryOperatorChoice->GetSelection())
1746 {
1747 case qryOpEQ:
1748 case qryOpLT:
1749 case qryOpGT:
1750 case qryOpLE:
1751 case qryOpGE:
1752 pQueryCol2Choice->Enable(TRUE);
1753 if (pQueryCol2Choice->GetSelection()) // Column name is highlighted
1754 {
1755 pQueryValue1Msg->Show(FALSE);
1756 pQueryValue1Txt->Show(FALSE);
1757 }
1758 else // "Value" is highlighted
1759 {
1760 pQueryValue1Msg->Show(TRUE);
1761 pQueryValue1Txt->Show(TRUE);
1762 pQueryValue1Txt->SetFocus();
1763 }
1764 break;
1765 case qryOpBEGINS:
1766 case qryOpCONTAINS:
1767 case qryOpLIKE:
1768 pQueryCol2Choice->SetSelection(0);
1769 pQueryCol2Choice->Enable(FALSE);
1770 pQueryValue1Msg->Show(TRUE);
1771 pQueryValue1Txt->Show(TRUE);
1772 pQueryValue1Txt->SetFocus();
1773 break;
1774 case qryOpBETWEEN:
1775 pQueryCol2Choice->SetSelection(0);
1776 pQueryCol2Choice->Enable(FALSE);
1777 pQueryValue2Msg->Show(TRUE);
1778 pQueryValue2Txt->Show(TRUE);
1779 pQueryValue1Msg->Show(TRUE);
1780 pQueryValue1Txt->Show(TRUE);
1781 pQueryValue1Txt->SetFocus();
1782 break;
1783 }
1784
1785 return;
1786
1787 } // Operator choice box
1788
1789 // Column 2 choice
1790 if (widgetName == pQueryCol2Choice->GetName())
1791 {
1792 if (pQueryCol2Choice->GetSelection()) // Column name is highlighted
1793 {
1794 pQueryValue1Msg->Show(FALSE);
1795 pQueryValue1Txt->Show(FALSE);
1796 }
1797 else // "Value" is highlighted
1798 {
1799 pQueryValue1Msg->Show(TRUE);
1800 pQueryValue1Txt->Show(TRUE);
1801 pQueryValue1Txt->SetFocus();
1802 }
1803 return;
1804
1805 } // Column 2 choice
1806
1807 // Add button
1808 if (widgetName == pQueryAddBtn->GetName())
1809 {
1810 ProcessAddBtn();
1811 return;
1812
1813 } // Add button
1814
1815 // And button
1816 if (widgetName == pQueryAndBtn->GetName())
1817 {
1818 AppendToWhere(" AND\n");
1819 return;
1820
1821 } // And button
1822
1823 // Or button
1824 if (widgetName == pQueryOrBtn->GetName())
1825 {
1826 AppendToWhere(" OR\n");
1827 return;
1828
1829 } // Or button
1830
1831 // Left Paren button
1832 if (widgetName == pQueryLParenBtn->GetName())
1833 {
1834 AppendToWhere("(");
1835 return;
1836
1837 } // Left Paren button
1838
1839 // Right paren button
1840 if (widgetName == pQueryRParenBtn->GetName())
1841 {
1842 AppendToWhere(")");
1843 return;
1844
1845 } // Right Paren button
1846
1847 // Done button
1848 if (widgetName == pQueryDoneBtn->GetName())
1849 {
1850 // Be sure the where clause will not overflow the output buffer
1851 if (strlen(pQuerySqlWhereMtxt->GetValue()) > DB_MAX_WHERE_CLAUSE_LEN)
1852 {
1853 wxString s;
1fc5dd6f
JS
1854 s.Printf("Maximum where clause length exceeded.\nLength must be less than %d", DB_MAX_WHERE_CLAUSE_LEN+1);
1855 wxMessageBox(s,"Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
1856 return;
1857 }
1858 // Validate the where clause for things such as matching parens
1859 if (!ValidateWhereClause())
1860 return;
1861 // Copy the where clause to the output buffer and exit
1862 strcpy(pWhere, pQuerySqlWhereMtxt->GetValue());
1863 Close();
1864 return;
1865
1866 } // Done button
1867
1868 // Clear button
1869 if (widgetName == pQueryClearBtn->GetName())
1870 {
1fc5dd6f 1871 bool Ok = (wxMessageBox("Are you sure you wish to clear the Query?","Confirm",wxYES_NO|wxICON_QUESTION) == wxYES);
108106cf
JS
1872
1873 if (Ok)
1874 pQuerySqlWhereMtxt->SetValue("");
1875 return;
1876
1877 } // Clear button
1878
1879 // Count button
1880 if (widgetName == pQueryCountBtn->GetName())
1881 {
1882 wxBeginBusyCursor();
1883 ProcessCountBtn();
1884 wxEndBusyCursor();
1885 return;
1886
1887 } // Count button
1888
1889} // CqueryDlg::OnCommand
1890
1891
e3065973 1892void CqueryDlg::OnCloseWindow(wxCloseEvent& event)
108106cf
JS
1893{
1894 // Clean up
1895 if (colInf)
1896 {
1897 delete [] colInf;
1898 colInf = 0;
1899 }
1900
1901 if (dbTable)
1902 {
1903 delete dbTable;
1904 dbTable = 0;
1905 }
1906
1907 GetParent()->SetFocus();
65d7ddc4
GT
1908 while (wxIsBusy())
1909 wxEndBusyCursor();
108106cf 1910
e3065973
JS
1911 this->Destroy();
1912
1913} // CqueryDlg::OnCloseWindow()
108106cf
JS
1914
1915/*
1fc5dd6f 1916bool CqueryDlg::SetWidgetPtrs()
108106cf 1917{
1fc5dd6f 1918 bool abort = FALSE;
108106cf
JS
1919
1920 abort = abort || !(pQueryCol1Choice = (wxChoice *)GetWidgetPtr("QueryCol1Choice",this));
1921 abort = abort || !(pQueryNotCheck = (wxCheckBox *)GetWidgetPtr("QueryNotCheck",this));
1922 abort = abort || !(pQueryOperatorChoice = (wxChoice *)GetWidgetPtr("QueryOperatorChoice",this));
1923 abort = abort || !(pQueryCol2Choice = (wxChoice *)GetWidgetPtr("QueryCol2Choice",this));
1fc5dd6f
JS
1924 abort = abort || !(pQueryValue1Txt = (wxTextCtrl *)GetWidgetPtr("QueryValue1Txt",this));
1925 abort = abort || !(pQueryValue2Txt = (wxTextCtrl *)GetWidgetPtr("QueryValue2Txt",this));
108106cf
JS
1926 abort = abort || !(pQuerySqlWhereMtxt = (wxMultiText *)GetWidgetPtr("QuerySqlWhereMtxt",this));
1927 abort = abort || !(pQueryAddBtn = (wxButton *)GetWidgetPtr("QueryAddBtn",this));
1928 abort = abort || !(pQueryAndBtn = (wxButton *)GetWidgetPtr("QueryAndBtn",this));
1929 abort = abort || !(pQueryOrBtn = (wxButton *)GetWidgetPtr("QueryOrBtn",this));
1930 abort = abort || !(pQueryLParenBtn = (wxButton *)GetWidgetPtr("QueryLParenBtn",this));
1931 abort = abort || !(pQueryRParenBtn = (wxButton *)GetWidgetPtr("QueryRParenBtn",this));
1932 abort = abort || !(pQueryDoneBtn = (wxButton *)GetWidgetPtr("QueryDoneBtn",this));
1933 abort = abort || !(pQueryClearBtn = (wxButton *)GetWidgetPtr("QueryClearBtn",this));
1934 abort = abort || !(pQueryCountBtn = (wxButton *)GetWidgetPtr("QueryCountBtn",this));
1935 abort = abort || !(pQueryHelpBtn = (wxButton *)GetWidgetPtr("QueryHelpBtn",this));
1fc5dd6f 1936 abort = abort || !(pQueryHintMsg = (wxStaticText *)GetWidgetPtr("QueryHintMsg",this));
108106cf
JS
1937
1938 pFocusTxt = NULL;
1939
1940 return(widgetPtrsSet = !abort);
1941
1942} // CqueryDlg::SetWidgetPtrs
1943*/
1944
1945void CqueryDlg::AppendToWhere(char *s)
1946{
1947 wxString whereStr = pQuerySqlWhereMtxt->GetValue();
1948 whereStr += s;
1fc5dd6f 1949 pQuerySqlWhereMtxt->SetValue(whereStr);
108106cf
JS
1950
1951} // CqueryDlg::AppendToWhere()
1952
1953
1954void CqueryDlg::ProcessAddBtn()
1955{
1956 qryOp oper = (qryOp) pQueryOperatorChoice->GetSelection();
1957
1958 // Verify that eveything is filled in correctly
1959 if (pQueryCol2Choice->GetSelection() == 0) // "Value" is selected
1960 {
1961 // Verify that value 1 is filled in
1962 if (strlen(pQueryValue1Txt->GetValue()) == 0)
1963 {
1964 wxBell();
1965 pQueryValue1Txt->SetFocus();
1966 return;
1967 }
1968 // For the BETWEEN operator, value 2 must be filled in as well
1969 if (oper == qryOpBETWEEN &&
1970 strlen(pQueryValue2Txt->GetValue()) == 0)
1971 {
1972 wxBell();
1973 pQueryValue2Txt->SetFocus();
1974 return;
1975 }
1976 }
1977
1978 // Build the expression and append it to the where clause window
1979 wxString s = pQueryCol1Choice->GetStringSelection();
1980
1981 if (pQueryNotCheck->GetValue() && (oper != qryOpEQ))
1982 s += " NOT";
1983
1984 switch(oper)
1985 {
1986 case qryOpEQ:
1987 if (pQueryNotCheck->GetValue()) // NOT box is checked
1988 s += " <>";
1989 else
1990 s += " =";
1991 break;
1992 case qryOpLT:
1993 s += " <";
1994 break;
1995 case qryOpGT:
1996 s += " >";
1997 break;
1998 case qryOpLE:
1999 s += " <=";
2000 break;
2001 case qryOpGE:
2002 s += " >=";
2003 break;
2004 case qryOpBEGINS:
2005 case qryOpCONTAINS:
2006 case qryOpLIKE:
2007 s += " LIKE";
2008 break;
2009 case qryOpBETWEEN:
2010 s += " BETWEEN";
2011 break;
2012 }
2013
2014 s += " ";
2015
2016 int col1Idx = pQueryCol1Choice->GetSelection();
2017
1fc5dd6f
JS
2018 bool quote = FALSE;
2019 if (colInf[col1Idx].sqlDataType == SQL_VARCHAR ||
108106cf
JS
2020 oper == qryOpBEGINS ||
2021 oper == qryOpCONTAINS ||
2022 oper == qryOpLIKE)
2023 quote = TRUE;
2024
2025 if (pQueryCol2Choice->GetSelection()) // Column name
2026 s += pQueryCol2Choice->GetStringSelection();
2027 else // Column 2 is a "value"
2028 {
2029 if (quote)
2030 s += "'";
2031 if (oper == qryOpCONTAINS)
2032 s += "%";
2033 s += pQueryValue1Txt->GetValue();
2034 if (oper == qryOpCONTAINS || oper == qryOpBEGINS)
2035 s += "%";
2036 if (quote)
2037 s += "'";
2038 }
2039
2040 if (oper == qryOpBETWEEN)
2041 {
2042 s += " AND ";
2043 if (quote)
2044 s += "'";
2045 s += pQueryValue2Txt->GetValue();
2046 if (quote)
2047 s += "'";
2048 }
2049
1fc5dd6f 2050 AppendToWhere((char*) (const char*) s);
108106cf
JS
2051
2052} // CqueryDlg::ProcessAddBtn()
2053
2054
2055void CqueryDlg::ProcessCountBtn()
2056{
2057 if (!ValidateWhereClause())
2058 return;
2059
2060 if (dbTable == 0) // wxTable object needs to be created and opened
2061 {
65d7ddc4 2062 if (!(dbTable = new wxTable(pDB, masterTableName, 0, NULL, !QUERY_ONLY, DbConnectInf.defaultDir)))
108106cf
JS
2063 {
2064 wxMessageBox("Memory allocation failed creating a wxTable object.","Error...",wxOK | wxICON_EXCLAMATION);
2065 return;
2066 }
2067 if (!dbTable->Open())
2068 {
2069 wxString tStr;
2070 tStr = "ODBC error during Open()\n\n";
2071 tStr += GetExtendedDBErrorMsg(__FILE__,__LINE__);
1fc5dd6f 2072 wxMessageBox(tStr,"ODBC Error...",wxOK | wxICON_EXCLAMATION);
108106cf
JS
2073 return;
2074 }
2075 }
2076
2077 // Count() with WHERE clause
1fc5dd6f 2078 dbTable->where = (char*) (const char*) pQuerySqlWhereMtxt->GetValue();
108106cf
JS
2079 ULONG whereCnt = dbTable->Count();
2080
2081 // Count() of all records in the table
2082 dbTable->where = 0;
2083 ULONG totalCnt = dbTable->Count();
2084
2085 if (whereCnt > 0 || totalCnt == 0)
2086 {
2087 wxString tStr;
1fc5dd6f
JS
2088 tStr.Printf("%lu of %lu records match the query criteria.",whereCnt,totalCnt);
2089 wxMessageBox(tStr,"Notice...",wxOK | wxICON_INFORMATION);
108106cf
JS
2090 }
2091 else
2092 {
2093 wxString tStr;
1fc5dd6f
JS
2094 tStr.Printf("%lu of %lu records match the query criteria.\n\nEither the criteria entered produced a result set\nwith no records, or there was a syntactical error\nin the clause you entered.\n\nPress the details button to see if any database errors were reported.",whereCnt,totalCnt);
2095 wxMessageBox(tStr,"Notice...",wxOK | wxICON_INFORMATION);
108106cf
JS
2096 }
2097
2098 // After a wxMessageBox, the focus does not necessarily return to the
2099 // window which was the focus when the message box popped up, so return
2100 // focus to the Query dialog for certain
2101 SetFocus();
2102
2103} // CqueryDlg::ProcessCountBtn()
2104
2105
1fc5dd6f 2106bool CqueryDlg::ValidateWhereClause()
108106cf
JS
2107{
2108 wxString where = pQuerySqlWhereMtxt->GetValue();
2109
2110 if (where.Freq('(') != where.Freq(')'))
2111 {
2112 wxMessageBox("There are mismatched parenthesis in the constructed where clause","Error...",wxOK | wxICON_EXCLAMATION);
2113 return(FALSE);
2114 }
2115 // After a wxMessageBox, the focus does not necessarily return to the
2116 // window which was the focus when the message box popped up, so return
2117 // focus to the Query dialog for certain
2118 SetFocus();
2119
2120 return(TRUE);
2121
2122} // CqueryDlg::ValidateWhereClause()