/*----------------------------------------------------------------------------*/ /* */ /* Copyright (c) 2011-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. */ /* */ /*----------------------------------------------------------------------------*/ /* ooDialog User Guide Exercise 05: ProductView.rex - The ProductView component v01-00 03Jun12 Contains: classes "ProductView" and "AboutDialog". Pre-requisites: ProductView.dll, ProductView.h. Support\NumberOnlyEditEx.cls (copied from ooDialog Samples) Description: A sample Product View component - part of the sample Order Management application. Changes: v01-00 03Jun12: First version. ------------------------------------------------------------------------------*/ ::requires "ooDialog.cls" ::requires "Support\NumberOnlyEditEx.cls" ::requires "ProductModelData.rex" /*////////////////////////////////////////////////////////////////////////////// ============================================================================== ProductView v01-00 03Jun12 ----------- The "view" part of the Product business component. Changes: v01-00 03Jun12: First version. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ ::CLASS ProductView SUBCLASS ResDialog PUBLIC ::ATTRIBUTE dialogState PRIVATE -- States are: 'closable' or 'inUpdate". /*---------------------------------------------------------------------------- Class Methods: --------------------------------------------------------------------------*/ ::METHOD newInstance CLASS PUBLIC UNGUARDED say ".ProductView-newInstance-01: Start." -- Enable use of symbolic IDs in menu creation, and turn off AutoDetection -- (the third parameter: .Application~setDefaults("O", "ProductView.h", .false) -- Create an instance of ProductView and show it: dlg = .ProductView~new("res\ProductView.dll", IDD_PRODUCT_VIEW) say ".ProductView-newInstance-02: dlg~Activate." dlg~activate /*---------------------------------------------------------------------------- Instance Methods: --------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------- Dialog Setup Methods: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD init say "ProductView-init-01." -- called first (result of .ProductView~new) forward class (super) continue /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD activate UNGUARDED say "ProductView-activate-01." self~dialogState = "closable" self~execute("SHOWTOP","IDB_PROD_ICON") return /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD initDialog expose menuBar prodControls prodData say "ProductView-initDialog-01" menuBar = .BinaryMenuBar~new(self, IDR_PRODUCT_VIEW_MENU, , self, .true) prodControls = .Directory~new prodControls[ecProdNo] = self~newEdit("IDC_PROD_NO") prodControls[ecProdName] = self~newEdit("IDC_PROD_NAME") prodControls[ecProdPrice] = self~newEdit("IDC_PROD_LIST_PRICE") prodControls[ecUOM] = self~newEdit("IDC_PROD_UOM") prodControls[ecProdDescr] = self~newEdit("IDC_PROD_DESCRIPTION") prodControls[gbSizes] = self~newEdit("IDC_PROD_SIZE_GROUP") -- Do we ever need this? prodControls[rbSmall] = self~newRadioButton("IDC_PROD_RADIO_SMALL") prodControls[rbMedium] = self~newRadioButton("IDC_PROD_RADIO_MEDIUM") prodControls[rbLarge] = self~newRadioButton("IDC_PROD_RADIO_LARGE") prodControls[pbSaveChanges] = self~newPushButton("IDC_PROD_SAVE_CHANGES") self~connectButtonEvent("IDC_PROD_SAVE_CHANGES","CLICKED",saveChanges) -- Use NumberOnlyEditEx.cls to enforce numeric only entry for Price and UOM: prodControls[ecProdPrice]~initDecimalOnly(2,.false) -- 2 decimal places, no sign. prodControls[ecUOM]~initDecimalOnly(0,.false) -- 0 decimal places, no sign. prodControls[ecProdPrice]~connectCharEvent(onChar) prodControls[ecUOM]~connectCharEvent(onChar) prodData = self~getData -- Gets data from ProductModel into prodData self~showData -- Show the data /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*---------------------------------------------------------------------------- Event Handler Methods - MenuBar Events: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD updateProduct UNGUARDED expose prodControls say "ProductView-updateProduct-01." -- Enable the controls to allow changes to the data: prodControls[ecProdName]~setReadOnly(.false) prodControls[ecProdPrice]~setReadOnly(.false) prodControls[ecUOM]~setReadOnly(.false) prodControls[ecProdDescr]~setReadOnly(.false) prodControls[rbSmall]~enable prodControls[rbMedium]~enable prodControls[rbLarge]~enable self~enableControl("IDC_PROD_SAVE_CHANGES") prodControls[pbSaveChanges]~state = "FOCUS" -- Put input focus on the button self~tabToNext() -- put text cursor on Product Description -- (as if the user had pressed tab) self~dialogState = "inUpdate" /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD refreshData UNGUARDED self~disableControl("IDC_PROD_SAVE_CHANGES") self~showData self~dialogState = "closable" /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD print UNGUARDED say "ProductView-print-01" ret = InfoDialog(.HRS~printMsg) /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD close UNGUARDED say "ProductView-close-01" self~cancel /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD about UNGUARDED say "ProductView-about-01" dlg = .AboutDialog~new("ProductView.dll", IDD_PRODUCT_VIEW_ABOUT) dlg~execute("SHOWTOP") /*---------------------------------------------------------------------------- Event Handler Methods - PushButton Events: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "Save Changes" - Collects new data, checks if there has indeed been a change, and if not, issues a warning msg. Disables the button when valid changes made. */ ::METHOD saveChanges UNGUARDED expose prodControls prodData -- Transform data from view format (as in controls) to app format (a directory): newProdData = self~xformView2App(prodControls) -- Check if anything's changed; if not, show msgbox and exit with controls enabled. -- If changed, go on to validate data. result = self~checkForChanges(newProdData) if result = .false then do msg = .HRS~nilSaved hwnd = self~dlgHandle answer = MessageDialog(msg,hwnd,.HRS~updateProd,"OK","WARNING","DEFBUTTON2 APPLMODAL") return end -- Now validate data: result = self~validate(newProdData) -- returns a null string or error messages. -- Better would be a set of error numbers. -- If no problems, then show msgbox and go on to disable controls. if result = "" then do msg = .HRS~saved hwnd = self~dlgHandle answer = MessageDialog(msg,hwnd,.HRS~updateProd,"OK","INFORMATION","DEFBUTTON1 APPLMODAL") end -- If problems, then show msgbox and leave user to try again or refresh or exit. else do msg = result||.EndOfLine||.HRS~notSaved hwnd = self~dlgHandle answer = MessageDialog(msg,hwnd,.HRS~updateProd,"OK","ERROR","DEFBUTTON1 APPLMODAL") return end -- Send new data to be checked by CustomerModel object (not implemented). -- Disable controls that were enabled by menu "ActionsFile-->Update" selection: prodControls[ecProdName]~setReadOnly(.true) prodControls[ecProdDescr]~setReadOnly(.true) prodControls[ecProdPrice]~setReadOnly(.true) prodControls[ecUom]~setReadOnly(.true) if newProdData~size \= "S" then prodControls[rbSmall]~disable if newProdData~size \= "M" then prodControls[rbMedium]~disable if newProdData~size \= "L" then prodControls[rbLarge]~disable self~disableControl("IDC_PROD_SAVE_CHANGES") self~dialogState = "closable" prodData = newProdData prodData~list return /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*---------------------------------------------------------------------------- Event Handler Methods - Keyboard Events: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD onChar UNGUARDED -- called for each character entered in the price or UOM fields. forward to (arg(6)) /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD cancel -- If in the process of updating, then ask whether any changes should be -- thrown away and dialog closed. If yes then close by calling the superclass, -- else nop. If not in update, then close immediately say "ProductView-cancel-01." if self~dialogState = "inUpdate" then do ans = MessageDialog(.HRS~closeInUpdate, self~dlgHandle, .HRS~updateIP, "YESNO", "WARNING", "DEFBUTTON2") if ans = .PlainBaseDialog~IDYES then return self~cancel:super else nop end else return self~cancel:super /*---------------------------------------------------------------------------- Application Methods: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD getData -- Get data from the ProductModel: --expose prodData say "ProductView-getData-01." idProductModel = .local~my.idProductModel prodData = idProductModel~query -- prodData is of type ProductDT return prodData /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD showData -- Transfrom data (where necessary) to display format, and then disable controls. expose prodControls prodData say "ProductView-showData-01." -- Set data in controls: prodControls[ecProdNo]~setText( prodData~number ) prodControls[ecProdName]~setText( prodData~name ) -- Price in prodData has no decimal point - 2 decimal places are implied - hence /100 for display. prodControls[ecProdPrice]~setText(prodData~price/100 ) prodControls[ecUOM]~settext( proddata~uom ) prodControls[ecProdDescr]~setText(prodData~description ) size = prodData~size -- Disable controls prodControls[ecProdName]~setReadOnly(.true) prodControls[ecProdPrice]~setReadOnly(.true) prodControls[ecUOM]~setReadOnly(.true) prodControls[ecProdDescr]~setReadOnly(.true) prodControls[rbSmall]~disable prodControls[rbMedium]~disable prodControls[rbLarge]~disable -- But check correct button and enable it to highlight it to the user: select when size = "S" then do .RadioButton~checkInGroup(self,"IDC_PROD_RADIO_SMALL","IDC_PROD_RADIO_LARGE","IDC_PROD_RADIO_SMALL") prodcontrols[rbSmall]~enable end when size = "M" then do .RadioButton~checkInGroup(self,"IDC_PROD_RADIO_SMALL","IDC_PROD_RADIO_LARGE","IDC_PROD_RADIO_MEDIUM") prodcontrols[rbMedium]~enable end otherwise do .RadioButton~checkInGroup(self,"IDC_PROD_RADIO_SMALL","IDC_PROD_RADIO_LARGE","IDC_PROD_RADIO_LARGE") prodcontrols[rbLarge]~enable end end /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD checkForChanges -- Check whether any data has actually changed when "Save Changes" button -- has been pressed. Return .true if data changed, else returns .false. expose prodData use arg newProdData changed = .false select when newProdData~name \= prodData~name then changed = .true when newProdData~price \= prodData~price then changed = .true when newProdData~uom \= prodData~uom then changed = .true when newProdData~description \= prodData~description then changed = .true when newProdData~size \= ProdData~size then changed = .true otherwise nop end return changed /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD validate -- Validation: 1. Check price/UOM not changed > 50% up or down. -- 2. Cannot change from Large to Small without UOM increasing -- by at least 100%; nor from Small to Large without -- decreasing by more than 50%. -- 3. Product Description <= 100 characters. -- 4. Product Name <= 30 characters. -- Returns string of messages - the null string if all OK. expose prodData use arg newProdData msg = "" -- Check Price (catches decimal point errors also): price = prodData~price; newPrice = newProdData~price oldUom = prodData~uom; newUom = newProdData~uom -- 'oldUom - avoids name conflict with 'uom' in newProddata~uom. if ((price/oldUom)*1.5 < newPrice/newUom) | (newPrice/newUom < (price/oldUom)/2) then do msg = msg||.HRS~badRatio||" " end -- Check Size vs UOM: if prodData~size = "L" & newProdData~size = "S" - -- Large to Small & prodData~uom/2 < newProdData~uom then do msg = msg||.HRS~uomTooBig||" " end if prodData~size = "S" & newProdData~size = "L" - -- Small to Large & prodData~uom*2 > newProdData~uom then do msg = msg||.HRS~uomTooSmall||" " end -- Check Product Description length: if newProdData.description~length > 80 then do msg = msg||.HRS~descrTooBig||" " end -- Check Product Name length: if newProdData~name~length > 30 then do msg = msg||.HRS~prodNameTooBig end return msg /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::METHOD xformView2App -- Transforms Product Data from View form (in the GUI controls) to -- App form (a directory with address as an array). expose prodControls prodData = .ProductDT~new prodData~number = prodControls[ecProdNo]~gettext() prodData~name = prodControls[ecProdName]~getText() price = prodControls[ecProdPrice]~getText() -- Data entered has or assumes a decimal point; but data in "application" -- is a whole number (e.g. $42.42 is recorded in the database as "4242"). -- So re-format data from decimal to whole number: priceTwoDecs = price~format(,2) -- force 2 dec positions -- Re-display price properly formatted (in case the user did not format correctly - e.g. entered "42" or "38.4"): prodControls[ecProdPrice]~setText(priceTwoDecs) -- Now format price to "application" format: price = (priceTwoDecs*100)~format(,0) -- multiply by 100 and then force whole number. prodData~price = price prodData~uom = prodControls[ecUOM]~getText() prodData~description = prodControls[ecProdDescr]~getText() select when prodControls[rbSmall]~checked then prodData~size = "S" when prodControls[rbMedium]~checked then prodData~size = "M" otherwise prodData~size = "L" end return prodData /*============================================================================*/ /*////////////////////////////////////////////////////////////////////////////// ============================================================================== AboutDialog v01-00 03Jun12 ------------- The "About" class - shows a dialog box that includes a bitmap - part of the ProductView component. Changes: v01-00 03Jun12: First version. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ ::CLASS AboutDialog SUBCLASS ResDialog /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::method initDialog expose font image resImage = .ResourceImage~new(self) -- Create an instance of a resource image image = resImage~getImage(IDB_PROD_ICON) -- Create an image from the Product bitmap stImage = self~newStatic(IDC_PRODABT_ICON_PLACE)~setImage(image) -- Create a static text control and set the image in it font = self~createFontEx("Ariel", 12) -- Create up a largish font with which to display text and ... self~newStatic(IDC_PRODABT_STATIC2)~setFont(font) -- ... set the static text to use that font. -- Provide for a double-click in Product icon: self~connectStaticNotify("IDC_PRODABT_ICON_PLACE", "DBLCLK", showMsgBox) /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::method showMsgBox say "AboutDialog-showMsgBox-01." ans = MessageDialog(.HRS~aboutDblClick) /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ ::method leaving expose font image self~deleteFont(font) image~release() /*============================================================================*/ /*////////////////////////////////////////////////////////////////////////////// ============================================================================== Human-Readable Strings (HRS) v01-00 03Jun12 -------- The HRS class provides constant character strings for user-visible messages. Changes: v01-00 03Jun12: First version. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ ::CLASS HRS PRIVATE -- Human-Readable Strings ::CONSTANT aboutDblClick "You double-clicked!" ::CONSTANT badRatio "The new price/UOM ratio cannot be changed more than 50% up or down." ::CONSTANT closeInUpdate "Any changes made will be lost. Exit anyway?" ::CONSTANT descrTooBig "The Product Description is too long." ::CONSTANT nilSaved "Nothing was changed! Data not saved." ::CONSTANT notSaved "Changes Not Saved!" ::CONSTANT prodNameTooBig "The Product Name is too long." ::CONSTANT saved "Changes saved." ::CONSTANT uomTooBig "The new UOM is too large." ::CONSTANT uomTooSmall "The new UOM is too small." ::CONSTANT updateIP "Update in process" ::CONSTANT updateProd "Update Product" ::CONSTANT printMsg "The 'Print...' menu item is not yet implemented." /*============================================================================*/