/*----------------------------------------------------------------------------*/ /* */ /* Copyright (c) 2010-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. */ /* */ /*----------------------------------------------------------------------------*/ /** * Simple example of using the month calendar control. * * Although this example is relatively simple, it shows how to use a number of * features of the month calendar: * * * How to size a month calendar to its optimal size. * * How to restrict the range of months the calendar displays. * * How to determine which month(s) are currently displayed in the control. * * How to customize the calendar and display certain dates in bold. * * How to connect and respond to the GETDAYSTATE event. * * The application is for a hypothetical company, used to show the paid holidays * for the current year. The dialog displays a month calendar for the current * year and as the user pages through the months, the paid holidays are high- * lighted in the calendar by bolding the dates. * * Note that to keep this simple, the "current" year is rather hard-coded. * However, it would not be too hard to enhance the program to calculate the * paid holidays dynamically. * * To fully understand this program, please read the ooDialog doc. */ sd = locate() .application~useGlobalConstDir("O", sd"paidHolidays.h") dlg = .HolidayCalendarDlg~new(sd"paidHolidays.rc", IDD_HOLIDAY_DLG) if dlg~initCode == 0 then do dlg~execute("SHOWTOP", IDI_DLG_OOREXX) end else do say say "Error creating dialog, aborting." return 99 end return 0 ::requires "ooDialog.cls" ::class 'HolidayCalendarDlg' subclass RcDialog ::method initDialog expose calendar -- Connect the GETDAYSTATE event. self~connectMonthCalendarEvent(IDC_MC_HOLIDAYS, "GETDAYSTATE", onGetDayState) calendar = self~newMonthCalendar(IDC_MC_HOLIDAYS) -- Size the calendar to its optimal size self~sizeCalendar(calendar) -- Restrict the calendar so that it only displays the year 2011. start = .DateTime~fromStandardDate("20110101") end = .DateTime~fromStandardDate("20111231") calendar~setRange(.array~of(start, end)) -- Determine which months are currently displayed in the calendar. The first -- and last months showing are returned in the passed in array as .DateTime -- objects. The return from getMonthRange() is the number of months -- displayed. The 'P' argument means to include Partially shown months. monthsShowing = .array~new count = calendar~getMonthRange(monthsShowing, 'P') -- Bold the appropriate days in the month(s) currently showing. To do that, -- we need an array of .DayState objects. dayStates = self~getDayStateArray(monthsShowing[1], count) calendar~setDayState(dayStates) -- This is the GETDAYSTATE event handler. The underlying month calendar sends -- this notification whenever it needs to refresh the calendar display for a -- new month. The arguments are a .DateTime object that specifies the first -- month to get the day state for and the second argument is the count of months -- needed. To reply to this notification we need to return a buffer containing -- the required day states. Creating the buffer correctly needs to be done -- through the .DayStates class, where a class method is provided to create the -- buffer. ::method onGetDayState unguarded expose calendar use arg startDate, count dayStates = self~getDayStateArray(startDate, count) buffer = .DayStates~makeDayStateBuffer(dayStates) return buffer -- This function resizes the calendar to its optimal size. The getMinRect() -- method of the MonthCalendar returns the minimum required size to completely -- display a full month. The method fills in the .Rect object with the correct -- size. The top and left attributes in the returned .Rect are always set to 0, -- so the right and bottom attributes are the width and height, in pixels, -- needed for the calendar. We then use the return to adjust the size of the -- calendar, while leaving the postion of the top, left corner of the calendar -- at the same spot. -- -- The dialog was originally designed on XP using a resource editor, and looked -- great. But, on Vista and Windows 7, the calendar takes up more room and -- overlaps the static text and Ok button controls. So, if it is Vista or -- later, we adjust the size of the dialog and the position of those controls. -- The adjustment is really just a matter of getting some data, (sizes and -- current positions,) and doing the math. -- -- The same relative positioning of the controls is done. I.e., in the original -- dialog template the top of the Ok button is placed at the bottom edge of the -- month calendar control. The static text control is placed 1/2 the margin to -- the right of calendar, etc.. ::method sizeCalendar private use strict arg calendar r = .Rect~new if \ calendar~getMinRect(r) then return 0 calendar~resizeTo(.Size~new(r~right, r~bottom)) if .OS~isAtLeastVista then do static = self~newStatic(IDC_ST_MSG) button = self~newPushButton(IDOK) -- Get the current window rectangles of the controls and mapped them to the -- client area of the dialog. mcRect = calendar~windowRect self~screen2client(mcRect) stRect = static~windowRect self~screen2client(stRect) okRect = button~windowRect self~screen2client(okRect) -- The position of the top left corner of the month calendar control defines -- the margins marginLeft = mcRect~left marginTop = mcRect~top -- Calculate what the new size of the dialog will need to be to contain the -- moved controls and resize the dialog to that. newWidth = (marginLeft * 2) + (marginLeft % 2) + (mcRect~right - mcRect~left) + (stRect~right - stRect~left) newHeight = (marginTop * 2) + (mcRect~bottom - mcRect~top) + (okRect~bottom - okRect~top) -- The new size of the dialog has to take into account the size of the -- window frame and the caption bar. newWidth += .SM~cxFixedFrame * 2 newHeight += (.SM~cyFixedFrame * 2) + .SM~cyCaption self~resizeTo(newWidth, newHeight) -- Move the static control to the right of the month calendar with a space -- 1/2 the width of the marging between them newX = mcRect~right + (marginLeft % 2) static~moveTo(newX, stRect~top) -- Move the Ok button so that the top edge of the button aligns with the -- bottom edge of the month calendar and the right edge of the button is -- even with the margin on the right. (Left and right margins are equal.) clRect = self~clientRect newX = clRect~right - marginLeft - (okRect~right - okRect~left) button~moveTo(newX, mcRect~bottom) end -- This function returns an array of .DayState objects for the specified months. -- A day state object specifies which days in a month should be bolded. ::method getDayStateArray private unguarded use strict arg startMonth, count -- Create the array to hold the .DayState objects. d = .array~new(count) -- We know that the month calendar always asks for any partially displayed -- months. We also know that our calendar only displays 1 month completely, -- and displays the partial month preceding and the partial month following. -- Therefore, if the complete month is January 2011, the preceding month will -- be December 2010. Likewise we know that the range set for the calendar is -- January 2011 through December 2011, January 2012 can never be shown, so -- December 2011 can never be a preceding month. Therefore, if the start -- month is 12, it must be December 2010. We give that month a 0 number so -- that getDayState() will return an empty .DayState object. month = startMonth~month if month == 12 then month = 0 do i = 1 to count d[i] = self~getDayState(month + i - 1) end return d -- This function initalizes a .DayState object to the proper value depending on -- the month specified. ::method getDayState private unguarded use strict arg month select when month == 1 then ds = .DayState~new(17) when month == 2 then ds = .DayState~new(21) when month == 3 then ds = .DayState~new when month == 4 then ds = .DayState~new when month == 5 then ds = .DayState~new(30) when month == 6 then ds = .DayState~new when month == 7 then ds = .DayState~new(4) when month == 8 then ds = .DayState~new when month == 9 then ds = .DayState~new(5) when month == 10 then ds = .DayState~new when month == 11 then ds = .DayState~new(24, 25) when month == 12 then ds = .DayState~new(23, 30) otherwise ds = .DayState~new() end -- End select return ds