/*----------------------------------------------------------------------------*/ /* */ /* Copyright (c) 2013-2014 Rexx Language Association. All rights reserved. */ /* */ /* This program and the accompanying materials are made available under */ /* the terms of the Common Public License v1.0 which accompanies this */ /* distribution. A copy is also available at the following address: */ /* https://www.oorexx.org/license.html */ /* */ /* Redistribution and use in source and binary forms, with or */ /* without modification, are permitted provided that the following */ /* conditions are met: */ /* */ /* Redistributions of source code must retain the above copyright */ /* notice, this list of conditions and the following disclaimer. */ /* Redistributions in binary form must reproduce the above copyright */ /* notice, this list of conditions and the following disclaimer in */ /* the documentation and/or other materials provided with the distribution. */ /* */ /* Neither the name of Rexx Language Association nor the names */ /* of its contributors may be used to endorse or promote products */ /* derived from this software without specific prior written permission. */ /* */ /* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */ /* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */ /* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */ /* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */ /* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ /* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */ /* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */ /* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */ /* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */ /* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */ /* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* */ /*----------------------------------------------------------------------------*/ /** * This example shows how to use the standard dialog, the ProgressDialog * dialog, to show the user how a lengthy operation is progressing. It also * demonstrates how much faster an internal sort can be than using a Rexx sort. * * The main points of the example are: * * 1.) It shows how to use the ProgressDialog class. * * 2.) It shows how to use the LvFullRow, LvItem, and LvSubItem objects to add * items to list-views * * 3.) It shows to sort on a column in the list-view * * Note that in the .rc file, the resource script file, for this example, the * dialog is created *not* visible. Thus, while the dialog is being * initialized, it is not visible on the screen. This eliminates flicker while * the items are being inserted into the list-view. Some people like the users * of their applications to see the list-view being filled, some people dislike * flicker. So, it is mostly a matter of preference how you create the dialog, * initially visible, or initially invisible. But, it is good to be aware of * the option. * * In addition, with a large number of list-view items, it takes some time to * create the LvFullRow objects and insert them into the list-view. With the * dialog being invisible, the user is going to wonder what is going on. This * is the perfect time to use a ProgressDialog. * * NOTE: During a Rexx sort of the items in a list-view, the list-view invokes * the Rexx method in this dialog to do a comparison of 2 items. We do * not know what sorting algorithm the list-view is using, but we can * speculate that it is a quick sort. Each callback from the native code * the list-view is running in, to the Rexx interpreter, is relatively * expensive. Expensive relative to the time to actually do a single * comparison. As the number of items to sort grows, the number of * comparisons grows, worst case, exponentially. Even average case, * O(n log n), the growth is far from linear. As the number of * comparisons grows, the expensive callbacks into the interpreter truely * dominate. Do not use the Rexx sort for a large number of items. * Start by using a Rexx sort with a small number of items to get a feel * for how long it is taking. Using a Rexx sort with a large nubmer of * items, will always finish, eventually. It may literally take hours. * * This example times the operations where a progress dialog is displayed and * shows those times in the main dialog. This is an interesting experiment. * * The internal sort is very fast, in comparison. Do not hesitate to sort * even the maximum number of items using the internal sort. */ -- Ensure we can be run from any directory. srcDir = locate() -- Set the defaults for this application. Use the global .constDir 'O'nly, -- Read the 'addManyRows.h' file for symbolic resource ID definitions. -- And, turn automatica data detection off (.false.) .application~setDefaults('O', srcDir'resources\addManyRows.h', .false) -- Allow the user to pick the number of items to be inserted into the -- list-view. dlgIntro = .ExampleSetUpDlg~new(srcDir'resources\addManyRows.rc', IDD_INTRO) if dlgIntro~execute('SHOWTOP') == dlgIntro~IDCANCEL then do ret = MessageDialog('You are missing out on an excellent example', ,'User Canceled', 'OK', 'WARNING') return 99 end dlg = .AddManyRowsDlg~new(srcDir'resources\addManyRows.rc', IDD_ADD_ROWS) if dlg~initCode = 0 then do dlg~itemCount = dlgIntro~selectedCount dlg~execute("SHOWTOP") end return 0 -- End of entry point. ::requires "ooDialog.cls" /** AddManyRowsDlg * * Our main example dialog subclass. */ ::class 'AddManyRowsDlg' subclass RcDialog ::attribute itemCount ::attribute createRowsTime ::attribute insertRowsTime ::attribute insertedRows ::attribute internalSortTime ::attribute rexxSortTime /** initDialog() * * initDialog is the place to do any initialization that requires the underlying * dialog to exist. Here we add some extended list-view styles and fill the * list-view with its items. */ ::method initDialog expose list rows createCanceled insertCanceled list = self~newListView(IDC_LV) list~addExtendedStyle("FULLROWSELECT GRIDLINES CHECKBOXES HEADERDRAGDROP SUBITEMIMAGES") list~InsertColumn(0, "Line text", 75) list~InsertColumn(1, "Line number", 55) list~InsertColumn(2, "Numbers", 50) list~InsertColumn(3, "Characters", 55) list~InsertColumn(4, "Characters", 55) list~InsertColumn(5, "Characters", 55) list~InsertColumn(6, "Characters", 55) list~InsertColumn(7, "Characters", 55) list~InsertColumn(8, "Characters", 55) list~InsertColumn(9, "Characters", 55) list~InsertColumn(10, "Characters", 55) list~InsertColumn(11, "Characters", 55) self~connectButtonEvent(IDC_PB_SORT_INTERN, 'CLICKED', onSortInternally) self~connectButtonEvent(IDC_PB_SORT_REXX, 'CLICKED', onSortRexx) rows = self~createRows(list) self~newGroupBox(IDC_GB_TIMES)~setText('Time in seconds (' || self~itemCount || ' rows):') self~setStatics /** setStatics * * Helper method to set up the static controls that are used to display the * timings. How they are set is dependent on whether the user canceled the * LvFullRow creation or the insertion of the rows. */ ::method setStatics private expose createCanceled insertCanceled staticInternal staticRexxSort staticCreate = self~newStatic(IDC_ST_CREATE) staticInsert = self~newStatic(IDC_ST_INSERT) staticInternal = self~newStatic(IDC_ST_INTERNAL) staticRexxSort = self~newStatic(IDC_ST_REXXSORT) if createCanceled then do staticCreate~setText('Create full rows:' self~createRowsTime '(canceled)') staticInsert~setText('Insert full rows: N/A') staticInternal~hide staticRexxSort~hide self~newPushButton(IDC_PB_SORT_INTERN)~disable self~newPushButton(IDC_PB_SORT_REXX)~disable rect = staticCreate~windowRect s = .Size~new(rect~right - rect~left, rect~bottom - rect~top) s~width *= 2 staticCreate~resizeTo(s) return 0 end staticCreate~setText('Create full rows:' self~createRowsTime) if insertCanceled then do staticInsert~setText('Insert full rows:' self~insertRowsTime '(canceled after inserting' self~insertedRows 'rows)') staticInternal~hide staticRexxSort~hide self~newPushButton(IDC_PB_SORT_INTERN)~disable self~newPushButton(IDC_PB_SORT_REXX)~disable rect = staticInsert~windowRect s = .Size~new(rect~right - rect~left, rect~bottom - rect~top) s~width *= 2 staticInsert~resizeTo(s) return 0 end staticInsert~setText('Insert full rows:' self~insertRowsTime) /** onSortInternally() * * The event handler for the Internal Sort push button. We put up a dialog to * let the user decide what and how to sort. */ ::method onSortInternally unguarded expose list staticInternal dlg = .SortSetupDlg~new(.application~srcDir'resources\addManyRows.rc', IDD_SORT_PARAMS) if dlg~execute('SHOWTOP') == dlg~IDCANCEL then return 0 d = .directory~new d~column = dlg~column d~ascending = dlg~ascending d~caseless = dlg~caseless j = time('E') list~sortItems('InternalListViewSort', d) self~internalSortTime = time('e') staticInternal~setText('Internal sort:' self~internalSortTime) /** onSortRexx() * * The event handler for the Rexx Sort push button. We put up a dialog to * let the user decide what and how to sort. We also warn the user that the * sort can take a very long time depending on the number of items in the * list-view */ ::method onSortRexx unguarded expose list staticRexxSort rexxColumn rexxAscending rexxCaseless count = self~itemCount if count > 1500 & count <= 2000 then do msg = "A Rexx sort with" count 'items can take' || .endOfLine || - 'some time. Please be patient.' || .endOfLine~copies(2) end else if count > 2000 then do msg = "A Rexx sort with" count 'items is not a' || .endOfLine || - 'good idea, it can take too long.' || .endOfLine~copies(2) || - 'If you want to perform this sort out of' || .endOfLine || - 'curiousity, start the sort and then let' || .endOfLine || - 'it alone. Windows will say the window' || .endOfLine || - 'is not responding. But it will event-' || .endOfLine || - 'ually finish. 5 minutes for 5000 items.' || .endOfLine || - 'Times are exponential for more items.' || .endOfLine~copies(2) end if msg~length > 3 then do msg || = 'Do you want to continue?' title = 'Cautionary Statement - Think Twice' if MessageDialog(msg, self~hwnd, title, 'YESNO', 'WARNING') == self~IDNO then return 0 end dlg = .SortSetupDlg~new(.application~srcDir'resources\addManyRows.rc', IDD_SORT_PARAMS) if dlg~execute('SHOWTOP') == dlg~IDCANCEL then return 0 rexxColumn = dlg~column rexxAscending = dlg~ascending rexxCaseless = dlg~caseless msg = 'Performing a list-view item sort using a Rexx method of this dialog. Please be patient...' capt = 'Rexx ooDialog Dialog Method Sort' pbDlg = .ProgressDialog~new(capt, msg) pbDlg~msgHeight = 2 pbDlg~marqueeMode = .true pbDlg~marqueePause = 50 pbDlg~noStatus = .true j = time('E') pbDlg~begin reply 0 list~sortItems('DOREXXSORT') pbDlg~endNow self~rexxSortTime = time('e') staticRexxSort~setText('Rexx sort:' self~rexxSortTime) /** doRexxSort() * * This is the call back method that actually does the comparison for 2 items in * the list view. */ ::method doRexxSort unguarded expose rexxColumn rexxAscending rexxCaseless use arg lvRow1, lvRow2 if rexxColumn == 0 then do text1 = lvRow1~item~text text2 = lvRow2~item~text end else do text1 = lvRow1~subitem(rexxColumn)~text text2 = lvRow2~subitem(rexxColumn)~text end if rexxAscending then do if rexxCaseless then return text1~caselessCompareTo(text2) else return text1~compareTo(text2) end else do if rexxCaseless then return text2~caselessCompareTo(text1) else return text2~compareTo(text1) end /** createRows() * * Here we create LvFullRow objects for every list-view item. LvItem objects * represent the list-view item and LvSubItem objects represent each column in * the list-view item. * * All the full row objects are put into an array. The array is then used to * insert all the items into the list-view. */ ::method createRows private expose createCanceled insertCanceled use arg list j = time('E') createCanceled = .false insertCanceled = .false count = self~itemCount rows = .array~new(count) step = (count * 2) / 100 if \ step~datatype('W') then step = trunc(step) pbDlg = .ProgressDialog~new pbDlg~msgText = 'Creating and inserting full rows into the list-view. This will take some time.' if self~itemCount > 30000 then do extra = .endOfLine~copies(2) || 'It is possible that inserting' self~itemCount 'items will exhaust your system resources.' pbDlg~msgText || = extra pbDlg~msgHeight = 5 end a = .Alerter~new pbDlg~setInterruptible(a) pbDlg~begin pbDlg~updateStatus('0 full rows created') do i = 1 to count j = i - 1 lvi = .LvItem~new(j, 'Line' i) lvsi1 = .LvSubItem~new(j, 1, i) lvsi2 = .LvSubItem~new(j, 2, random(1, 200)) lvsi3 = .LvSubItem~new(j, 3, self~randomChars) lvsi4 = .LvSubItem~new(j, 4, self~randomChars) lvsi5 = .LvSubItem~new(j, 5, self~randomChars) lvsi6 = .LvSubItem~new(j, 6, self~randomChars) lvsi7 = .LvSubItem~new(j, 7, self~randomChars) lvsi8 = .LvSubItem~new(j, 8, self~randomChars) lvsi9 = .LvSubItem~new(j, 9, self~randomChars) lvsi10 = .LvSubItem~new(j, 10, self~randomChars) lvsi11 = .LvSubItem~new(j, 11, self~randomChars) rows[i] = .LvFullRow~new(lvi, lvsi1, lvsi2, lvsi3, lvsi4, lvsi5, lvsi6, lvsi7, lvsi8, lvsi9, lvsi10, lvsi11, .true) if i // step = 0 then do pbDlg~increase pbDlg~updateStatus(i 'full rows created') end if a~isCanceled then do pbDlg~updateStatus('canceled after creating' i 'full rows') r = SysSleep(1.5) pbDlg~endNow leave end end self~createRowsTime = time('e') self~itemCount = rows~items if a~isCanceled then do createCanceled = .true return rows end j = time('r') pbDlg~updateStatus('0 full rows inserted') list~prepare4nItems(rows~items) do i = 1 to rows~items list~addFullRow(rows[i]) if i // step = 0 then do pbDlg~increase pbDlg~updateStatus(i 'full rows inserted') end if a~isCanceled then do pbDlg~updateStatus('canceled after inserting' i 'full rows') r = SysSleep(1.5) pbDlg~endNow leave end end self~insertRowsTime = time('e') if a~isCanceled then do self~insertedRows = i insertCanceled = .true end else do pbDlg~complete pbDlg~updateStatus('finished inserting' rows~items 'full rows') r = SysSleep(1.5) pbDlg~endNow end return rows /** randomChars() * * Simple method to generate some random string of characters. */ ::method randomChars private len = random(1, 7) chars = '' do i = 1 to len upper = random(0, 1) if upper then chars || = random(65, 90)~d2c else chars || = random(97, 122)~d2c end return chars /** ExampleSetUpDlg * * A simple helper dialog for our example. We let the user choose the number of * list-view items to insert. * * There is not much comment for this class, it is really pretty straight * forward, initialize the dialog controls to the beginning state, what until * the user closes the dialog, record the number of items the user picked. */ ::class 'ExampleSetUpDlg' subclass RcDialog ::attribute selectedCount ::method initDialog expose selectedCount updItems editItems chkFreeForm firstRB lastRB firstRB = .constDir[IDC_RB_1000] lastRb = .constDir[IDC_RB_25000] selectedCount = 5000 self~newRadioButton(IDC_RB_5000)~check updItems = self~newUpDown(IDC_SP_ITEMS) r = .directory~new r~min = 500 r~max = 50000 updItems~setRange(r) updItems~setPosition(10000) updItems~disable editItems = self~newEdit(IDC_ED_ITEMS)~~disable chkFreeForm = self~newCheckBox(IDC_CK_FREEFORM) self~connectButtonEvent(IDC_CK_FREEFORM, 'CLICKED', onClick) self~connectUpDownEVent(IDC_SP_ITEMS, 'DELTAPOS', onPosChange) ::method onClick unguarded expose chkFreeForm updItems editItems firstRB lastRB if chkFreeForm~checked then do updItems~enable editItems~enable do i = firstRB to lastRB self~newRadioButton(i)~disable end end else do updItems~disable editItems~disable do i = firstRB to lastRB self~newRadioButton(i)~enable end end ::method onPosChange unguarded use arg pos, delta, id, hwnd return .UpDown~deltaPosReply(.true, .false, delta * 100) ::method ok unguarded expose chkFreeForm updItems firstRB lastRB selectedCount if chkFreeForm~checked then do selectedCount = updItems~getPosition end else do j = 1 do i = firstRB to lastRB if self~newRadioButton(i)~checked then leave j += 1 end select when j = 1 then selectedCount = 1000 when j = 2 then selectedCount = 2000 when j = 3 then selectedCount = 5000 when j = 4 then selectedCount = 10000 when j = 5 then selectedCount = 15000 when j = 6 then selectedCount = 25000 otherwise selectedCount = 0 end -- End select end return self~ok:super /** SortSetUpDlg * * A dialog to let the user choose what and how they want soreted. * * Similar to the ExampleSetUpDlg class there is not much comment here, the * dialog is straight forward, set up the controls, record what the user pickes. */ ::class 'SortSetUpDlg' subclass RcDialog ::attribute column ::attribute ascending ::attribute caseless ::method initDialog expose column ascending caseless rbItem rbAscending rbCaseless upDown edit column = 0 ascending = .true caseless = .true rbItem = self~newRadioButton(IDC_RB_ITEM)~~check rbAscending = self~newRadioButton(IDC_RB_ASCENDING)~~check rbCaseless = self~newRadioButton(IDC_RB_CASELESS)~~check upDown = self~newUpDown(IDC_SP_COL)~~disable edit = self~newEdit(IDC_ED_COL)~~disable upDown~setRange(1, 11) upDown~setPosition(3) self~connectButtonEvent(IDC_RB_ITEM, 'CLICKED', onItemClick) self~connectButtonEvent(IDC_RB_SUBITEM, 'CLICKED', onSubItemClick) rbItem~assignFocus ::method onItemClick unguarded expose upDown edit upDown~disable edit~disable ::method onSubItemClick unguarded expose upDown edit upDown~enable edit~enable ::method ok unguarded expose column ascending caseless rbItem rbAscending rbCaseless upDown if rbItem~checked then column = 0 else column = upDown~getPosition if rbAscending~checked then ascending = .true else ascending = .false if rbCaseless~checked then caseless = .true else caseless = .false return self~ok:super