diff --git a/mainform1.pas b/mainform1.pas deleted file mode 100644 index 7fcd141..0000000 --- a/mainform1.pas +++ /dev/null @@ -1,961 +0,0 @@ -unit MainForm1; - -{$mode objfpc}{$H+} - -interface - -uses - Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, - Menus, StdCtrls, Spin, ExtCtrls, About, LCLType, IniFiles, Settings, MMSystem, - Windows, StrUtils, DateUtils; - -type - TMode = (Timer, Stopwatch); - TPosition = (Center, Remember, TopLeft, TopRight, BottomLeft, BottomRight); - - { TMainForm } - - TMainForm = class(TForm) - ImgIconMain: TImage; - ImgIconRunning: TImage; - ImgIconPaused: TImage; - ImgIconDone: TImage; - ImgStart: TImage; - ImgPause: TImage; - MainMenu1: TMainMenu; - MenuItem1: TMenuItem; - MenuItem2: TMenuItem; - MenuCompact: TMenuItem; - MenuReset: TMenuItem; - MenuToggle: TMenuItem; - MenuSave: TMenuItem; - PopupMenuCompact: TPopupMenu; - ImgReset: TImage; - TrayMenuOptions: TMenuItem; - TrayMenuShow: TMenuItem; - TrayMenuToggle: TMenuItem; - TrayMenuReset: TMenuItem; - TrayMenuExit: TMenuItem; - MenuCount: TMenuItem; - TrayMenuAbout: TMenuItem; - MenuOptions: TMenuItem; - MenuHelp: TMenuItem; - MenuAbout: TMenuItem; - MenuFile: TMenuItem; - MenuExit: TMenuItem; - MenuEdit: TMenuItem; - TrayMenu: TPopupMenu; - TimeLabel: TLabel; - Minutes: TSpinEdit; - Count: TStaticText; - Timer1: TTimer; - TrayIconMain: TTrayIcon; - procedure FormActivate(Sender: TObject); - procedure ToggleCompact(Sender: TObject); - procedure MinutesChanged(Sender: TObject); - procedure OnDestroyForm(Sender: TObject); - procedure OnShowForm(Sender: TObject); - procedure ShowAbout(Sender: TObject); - procedure CloseApp(Sender: TObject); - procedure OnCreateForm(Sender: TObject); - procedure Countdown(Sender: TObject); - procedure ResetCountdown(Sender: TObject); - procedure DisableTimer(); - procedure EnableTimer(); - procedure SetTimer(); - procedure ShowOptions(Sender: TObject); - procedure ToggleCountdown(Sender: TObject); - procedure FormKeyPress(Sender: TObject; var Key: char); - function SecondsToTime(Seconds: integer): string; - procedure FormWindowStateChange(Sender: TObject); - procedure UpdateTime(); - procedure SaveSettings(Sender: TObject); - class function GetFilePath(Path: string): string; - procedure PlayAudio(Path: string; Loop: boolean); - procedure StopAudio(); - procedure RunApp(Path: string); - procedure ShowDoneMessage(Msg: string); - procedure ShowTrayMessage(Msg: string); - function GetHandle(): HWND; - procedure SetDefaults(Sender: TObject); - function FontStylesToInt(Fnt: TFont): integer; - function IntToFontStyles(Mask: integer): TFontStyles; - procedure SetFieldsVisible(showFields: boolean); - procedure UpdateAlwaysOnTop(onTop: boolean); - procedure PlayTicking(); - function IsInteger(S: String): boolean; - procedure UpdateTimeCaption(newSecondsMode: boolean); - private - { private declarations } - IniFile: TIniFile; - AlwaysOnTop: boolean; - MinToTray: boolean; - AutoStart: boolean; - HideSeconds: boolean; - ClickTime: boolean; - DblClickTime: boolean; - AutoRestart: boolean; - LoopAudio: boolean; - AudioPlaying: boolean; - TickingOn: boolean; - AutoSave: boolean; - DoneMessage: string; - DoneMessageEnabled: boolean; - DoneTrayMsg: string; - DoneTrayMsgEnabled: boolean; - DoneAudio: string; - DoneAudioEnabled: boolean; - DoneApp: string; - DoneAppEnabled: boolean; - WndPosition: TPosition; - WndPositions: TStrings; - WndHeight: integer; - WndWidth: integer; - WndTop: integer; - WndLeft: integer; - WndHandle: HWND; - SecondsMode: boolean; // Undocumented mode to treat minutes as seconds - Mode: TMode; - CompactMode: boolean; - FontSizeChanged : boolean; - //FormerWidth : integer; - //FormerHeight : integer; - EndTime: TDateTime; - StartTime: TDateTime; - CountdownDone: boolean; - public - { public declarations } - end; - -var - MainForm: TMainForm; - AppName: string; - IniFilename: string; - -implementation - -{ TMainForm } - -const - APP_NAME = 'SnapTimer'; - INI_SEC_MAIN = 'Main'; - INI_SEC_ALARMS = 'Alarms'; - INI_SEC_PLACEMENT = 'Placement'; - INI_SEC_FONTS = 'Fonts'; - INI_MINUTES = 'Minutes'; - INI_ALWAYS_ON_TOP = 'AlwaysOnTop'; - INI_MIN_TO_TRAY = 'MinToTray'; - INI_AUTOSTART = 'AutoStart'; - INI_HIDESECONDS = 'HideSeconds'; - INI_CLICKTIME = 'ClickTime'; - INI_DBLCLICKTIME = 'DoubleClickTime'; - INI_AUTORESTART = 'AutoRestart'; - INI_LOOP_AUDIO = 'LoopAudio'; - INI_TICKING_ON = 'TickingEnabled'; - INI_AUTOSAVE = 'AutoSave'; - INI_HEIGHT = 'WinHeight'; - INI_WIDTH = 'WinWidth'; - INI_LEFT = 'WinLeft'; - INI_TOP = 'WinTop'; - INI_POSITION = 'WinPosition'; - INI_DONE_MESSAGE = 'Message'; - INI_DONE_MESSAGE_ON = 'MessageEnabled'; - INI_DONE_TRAY_MESSAGE = 'TrayMessage'; - INI_DONE_TRAY_MESSAGE_ON = 'TrayMessageEnabled'; - INI_DONE_AUDIO = 'AudioFile'; - INI_DONE_AUDIO_ON = 'AudioFileEnabled'; - INI_DONE_APP = 'RunApp'; - INI_DONE_APP_ON = 'RunAppEnabled'; - INI_SECONDS_MODE = 'SecondsMode'; - INI_FONT_NAME = 'Name'; - INI_FONT_CHARSET = 'Charset'; - INI_FONT_COLOR = 'Color'; - INI_FONT_SIZE = 'Size'; - INI_FONT_STYLE = 'Style'; - INI_BG_COLOR = 'BgColor'; - - // Menu items - MENU_FILE = '&File'; - MENU_EXIT = 'E&xit'; - MENU_EDIT = '&Edit'; - MENU_OPTIONS = 'Op&tions'; - MENU_HELP = '&Help'; - MENU_ABOUT = '&About'; - TRAY_MENU_SHOW = 'Show application'; - - // Buttons - BTN_START = '&Start'; - BTN_PAUSE = '&Pause'; - BTN_RESET = '&Reset'; - - // Labels - LBL_MINUTES = '&Minutes:'; - LBL_SECONDS = '&Seconds:'; - - // Default values - DEF_TIME = 15; - DEF_DONE_MSG = 'Time''s up'; - DEF_DONE_MSG_ON = True; - DEF_DONE_TRAY_MSG = 'Countdown completed'; - DEF_DONE_TRAY_MSG_ON = False; - DEF_DONE_AUDIO = '.\sounds\alarm_clock_bell.wav'; - DEF_DONE_AUDIO_ON = False; - DEF_DONE_APP = ''; - DEF_DONE_APP_ON = False; - DEF_ALWAYS_ON_TOP = False; - DEF_MIN_TO_TRAY = False; - DEF_AUTOSTART = False; - DEF_HIDESECONDS = False; - DEF_CLICKTIME = True; - DEF_DBLCLICKTIME = True; - DEF_AUTORESTART = False; - DEF_LOOP_AUDIO = False; - DEF_TICKING_ON = False; - DEF_TICKING_PATH = '.\sounds\ticking\ticking.wav'; - DEF_AUTOSAVE = True; - DEF_SECONDS_MODE = False; - DEF_POSITION = Ord(Center); - DEF_HEIGHT = 149; - DEF_WIDTH = 214; - DEF_FONT_NAME = 'Arial'; - DEF_FONT_CHARSET = 0; - DEF_FONT_COLOR = clNavy; - DEF_FONT_SIZE = 38; - DEF_FONT_STYLE = 0; - DEF_BG_COLOR = clNone; - - // Messages - MSG_OPEN_INI = 'An error occurred opening the .ini file, settings won''t be saved'; - MSG_WRITE_INI = - 'An error occurred trying to write to the .ini file, settings won''t be saved.'; - -procedure TMainForm.OnCreateForm(Sender: TObject); -begin - MenuFile.Caption := MENU_FILE; - MenuToggle.Caption := BTN_START; - MenuReset.Caption := BTN_RESET; - MenuExit.Caption := MENU_EXIT; - MenuEdit.Caption := MENU_EDIT; - MenuOptions.Caption := MENU_OPTIONS; - MenuHelp.Caption := MENU_HELP; - MenuAbout.Caption := MENU_ABOUT; - - TrayMenuShow.Caption := TRAY_MENU_SHOW; - TrayMenuToggle.Caption := BTN_START; - TrayMenuReset.Caption := BTN_RESET; - TrayMenuAbout.Caption := MENU_ABOUT; - TrayMenuExit.Caption := MENU_EXIT; - - // Set all defaults, then load from .ini - Minutes.Value := DEF_TIME; - AlwaysOnTop := DEF_ALWAYS_ON_TOP; - MinToTray := DEF_MIN_TO_TRAY; - AutoStart := DEF_AUTOSTART; - HideSeconds := DEF_HIDESECONDS; - ClickTime := DEF_CLICKTIME; - DblClickTime := DEF_DBLCLICKTIME; - AutoRestart := DEF_AUTORESTART; - LoopAudio := DEF_LOOP_AUDIO; - TickingOn := DEF_TICKING_ON; - AutoSave := DEF_AUTOSAVE; - DoneMessage := DEF_DONE_MSG; - DoneMessageEnabled := DEF_DONE_MSG_ON; - DoneTrayMsg := DEF_DONE_TRAY_MSG; - DoneTrayMsgEnabled := DEF_DONE_TRAY_MSG_ON; - DoneAudio := DEF_DONE_AUDIO; - DoneAudioEnabled := DEF_DONE_AUDIO_ON; - DoneApp := DEF_DONE_APP; - DoneAppEnabled := DEF_DONE_APP_ON; - SecondsMode := DEF_SECONDS_MODE; - WndPosition := TPosition(DEF_POSITION); - - WndPositions := TStringList.Create; - WndPositions.Add('Centered'); - WndPositions.Add('Remember position'); - WndPositions.Add('Top left'); - WndPositions.Add('Top right'); - WndPositions.Add('Bottom left'); - WndPositions.Add('Bottom right'); - - Mode := Timer; - CompactMode := False; - AudioPlaying := False; - Self.DoubleBuffered := True; - Count.DoubleBuffered := True; - Timer1.Interval := 150; - CountdownDone := False; - - AppName := ExtractFileName(Application.ExeName); - IniFilename := ExtractFilePath(Application.ExeName) + ChangeFileExt(AppName, '.ini'); - - // TODO For some reason this isn't working at all - //WriteLn('checking for options: ' + IntToStr(ParamCount)); - //If ParamCount > 1 then begin - // WriteLn('has options'); - //if Application.HasOption('h', 'help') then begin - // WriteLn('SnapTimer usage'); - // WriteLn('[number of minutes] (must be the only parameter)'); - // WriteLn('-h|--help\tShow this usage'); - // WriteLn('-i|--ini=\tPath to .ini file to use'); - //end else if Application.HasOption('i', 'ini') then begin - // //IniFilename := Application.GetOptionValue('i', 'ini'); - // WriteLn('ini option found'); - //end; - //end; - try - IniFile := TIniFile.Create(IniFilename); - Minutes.Value := IniFile.ReadInteger(INI_SEC_MAIN, INI_MINUTES, DEF_TIME); - AlwaysOnTop := IniFile.ReadBool(INI_SEC_MAIN, INI_ALWAYS_ON_TOP, DEF_ALWAYS_ON_TOP); - MinToTray := IniFile.ReadBool(INI_SEC_MAIN, INI_MIN_TO_TRAY, DEF_MIN_TO_TRAY); - AutoStart := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTOSTART, DEF_AUTOSTART); - HideSeconds := IniFile.ReadBool(INI_SEC_MAIN, INI_HIDESECONDS, DEF_HIDESECONDS); - ClickTime := IniFile.ReadBool(INI_SEC_MAIN, INI_CLICKTIME, DEF_CLICKTIME); - DblClickTime := IniFile.ReadBool(INI_SEC_MAIN, INI_DBLCLICKTIME, DEF_DBLCLICKTIME); - AutoRestart := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTORESTART, DEF_AUTORESTART); - LoopAudio := IniFile.ReadBool(INI_SEC_MAIN, INI_LOOP_AUDIO, DEF_LOOP_AUDIO); - TickingOn := IniFile.ReadBool(INI_SEC_MAIN, INI_TICKING_ON, DEF_TICKING_ON); - AutoSave := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTOSAVE, DEF_AUTOSAVE); - SecondsMode := IniFile.ReadBool(INI_SEC_MAIN, INI_SECONDS_MODE, DEF_SECONDS_MODE); - DoneMessage := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_MESSAGE, DEF_DONE_MSG); - DoneMessageEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_MESSAGE_ON, - DEF_DONE_MSG_ON); - DoneTrayMsg := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE, - DEF_DONE_TRAY_MSG); - DoneTrayMsgEnabled := IniFile.ReadBool(INI_SEC_ALARMS, - INI_DONE_TRAY_MESSAGE_ON, DEF_DONE_TRAY_MSG_ON); - DoneAudio := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_AUDIO, DEF_DONE_AUDIO); - DoneAudioEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_AUDIO_ON, - DEF_DONE_AUDIO_ON); - DoneApp := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_APP, DEF_DONE_APP); - DoneAppEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_APP_ON, DEF_DONE_APP_ON); - WndHeight := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_HEIGHT, DEF_HEIGHT); - WndWidth := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_WIDTH, DEF_WIDTH); - WndPosition := TPosition(IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_POSITION, DEF_POSITION)); - WndLeft := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_LEFT, 0); - WndTop := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_TOP, 0); - - Count.Font.Quality := fqAntialiased; - Count.Font.Name := IniFile.ReadString(INI_SEC_FONTS, INI_FONT_NAME, - DEF_FONT_NAME); - Count.Font.CharSet := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_CHARSET, - DEF_FONT_CHARSET); - Count.Font.Color := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_COLOR, - DEF_FONT_COLOR); - Count.Font.Size := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_SIZE, - DEF_FONT_SIZE); - Count.Font.Style := IntToFontStyles(IniFile.ReadInteger(INI_SEC_FONTS, - INI_FONT_STYLE, DEF_FONT_STYLE)); - Count.Color := IniFile.ReadInteger(INI_SEC_FONTS, INI_BG_COLOR, DEF_BG_COLOR); - - except - MessageDlg(MSG_OPEN_INI, mtError, [mbOK], 0); - end; - if Assigned(IniFile) then - IniFile.Free; - - // Use minutes argument if it's the only parameter and it's numeric - if (ParamCount = 1) and (IsInteger(ParamStr(1))) then - Minutes.Value := StrToInt(ParamStr(1)); - - UpdateTimeCaption(SecondsMode); - ResetCountdown(Sender); - if AutoStart then - ToggleCountdown(Sender); -end; - -procedure TMainForm.ResetCountdown(Sender: TObject); -begin - if (Sender.ClassName = Count.ClassName) and (not DblClickTime) then - Exit; - // Turn off both looping audio and ticking - StopAudio(); - DisableTimer(); - SetTimer(); - TrayIconMain.Icon := ImgIconMain.Picture.Icon; -end; - -procedure TMainForm.DisableTimer(); -begin - Timer1.Enabled := False; - ImgPause.Visible := False; - ImgStart.Visible := True; - MenuToggle.Caption := BTN_START; - TrayMenuToggle.Caption := BTN_START; - StopAudio(); -end; - -procedure TMainForm.EnableTimer(); -begin - Timer1.Enabled := True; - ImgPause.Visible := True; - ImgStart.Visible := False; - MenuToggle.Caption := BTN_PAUSE; - TrayMenuToggle.Caption := BTN_PAUSE; - TrayIconMain.Icon := ImgIconRunning.Picture.Icon; - - if TickingOn then - PlayTicking(); -end; - -procedure TMainForm.SetTimer(); -begin - if SecondsMode then - Timer1.Tag := Minutes.Value - else - Timer1.Tag := Minutes.Value * 60; - UpdateTime(); -end; - -procedure TMainForm.ShowOptions(Sender: TObject); -begin - OptionsForm := TOptionsForm.Create(Self); - with OptionsForm do - begin - NotifyMsg.Text := DoneMessage; - NotifyMsgOn.Checked := DoneMessageEnabled; - - NotifyTrayMsg.Text := DoneTrayMsg; - NotifyTrayMsgOn.Checked := DoneTrayMsgEnabled; - - NotifyAudio.Text := DoneAudio; - NotifyAudioOn.Checked := DoneAudioEnabled; - - NotifyRunApp.Text := DoneApp; - NotifyRunAppOn.Checked := DoneAppEnabled; - - CheckAlwaysOnTop.Checked := AlwaysOnTop; - CheckMinToTray.Checked := MinToTray; - CheckAutostart.Checked := AutoStart; - CheckHideSeconds.Checked := HideSeconds; - CheckClicktime.Checked := ClickTime; - CheckDblClickTime.Checked := DblClickTime; - CheckAutoRestart.Checked := AutoRestart; - CheckLoopAudio.Checked := LoopAudio; - CheckTicking.Checked := TickingOn; - CheckAutoSave.Checked := AutoSave; - CheckSecondsMode.Checked := SecondsMode; - - PositionCombo.Items := WndPositions; - PositionCombo.ItemIndex := Ord(WndPosition); - - PageControl1.TabIndex := 0; - - f := TFont.Create; - f.Name := Count.Font.Name; - f.Size := Count.Font.Size; - f.CharSet := Count.Font.CharSet; - f.Color := Count.Font.Color; - f.Style := Count.Font.Style; - BgColor.ButtonColor := Count.Color; - FontSizeChanged := False; - - if ShowModal = mrOk then - begin - UpdateTimeCaption(CheckSecondsMode.Checked); - - DoneMessage := NotifyMsg.Text; - DoneMessageEnabled := NotifyMsgOn.Checked; - DoneTrayMsg := NotifyTrayMsg.Text; - DoneTrayMsgEnabled := NotifyTrayMsgOn.Checked; - DoneAudio := NotifyAudio.Text; - DoneAudioEnabled := NotifyAudioOn.Checked; - DoneApp := NotifyRunApp.Text; - DoneAppEnabled := NotifyRunAppOn.Checked; - AlwaysOnTop := CheckAlwaysOnTop.Checked; - MinToTray := CheckMinToTray.Checked; - AutoStart := CheckAutostart.Checked; - HideSeconds := CheckHideSeconds.Checked; - ClickTime := CheckClicktime.Checked; - DblClickTime := CheckDblClickTime.Checked; - AutoRestart := CheckAutoRestart.Checked; - LoopAudio := CheckLoopAudio.Checked; - TickingOn := CheckTicking.Checked; - AutoSave := CheckAutoSave.Checked; - SecondsMode := CheckSecondsMode.Checked; - WndPosition := TPosition(PositionCombo.ItemIndex); - //if not (Count.Font.Size = f.Size) then - //begin - //FontSizeChanged := True; - //GetPreferredSize(FormerWidth, FormerHeight, True); - //ShowMessageFmt('width: %d, height: %d', [FormerWidth, FormerHeight]); - //end; - //Count.Font := f; - // TODO Get font colors and background color to be shown - what's up? - Count.Font.Name := f.Name; - Count.Font.Size := f.Size; - Count.Font.CharSet := f.CharSet; - Count.Font.Color := f.Color; - Count.Font.Style := f.Style; - Count.Font.Quality := fqAntialiased; - Count.Color := BgColor.ButtonColor; - - //if FontSizeChanged then OnShowForm(Sender); - - UpdateAlwaysOnTop(AlwaysOnTop); - - if Timer1.Enabled then - begin - if TickingOn then - PlayTicking - else - StopAudio(); - end; - - if AutoSave then - SaveSettings(Sender); - // TODO Resize the window if necessary to accomodate larger text - // Get the text size from the font for '00:00:00'? - - end; - end; - OptionsForm.Free; -end; - -procedure TMainForm.ToggleCountdown(Sender: TObject); -begin - if (Sender.ClassName = Count.ClassName) and (not ClickTime) then - Exit; - - if Minutes.Value = 0 then - begin - Mode := Stopwatch; - StartTime := Now - (Timer1.Tag * OneSecond); - end - else - begin - Mode := Timer; - EndTime := Now + (Timer1.Tag * OneSecond); - end; - - if Timer1.Enabled then - begin - TrayIconMain.Icon := ImgIconPaused.Picture.Icon; - DisableTimer(); - end - else - begin - if CountdownDone then - begin - // TODO When restarting after countdown has elapsed, quickly jumps from - // starting number to next one - fix? - CountdownDone := False; - ResetCountdown(Sender); - EndTime := Now + (Timer1.Tag * OneSecond); - end; - EnableTimer(); - end -end; - -procedure TMainForm.Countdown(Sender: TObject); -begin - if MODE = Stopwatch then - begin - Timer1.Tag := SecondsBetween(StartTime, Now); - UpdateTime(); - end - else - begin - if EndTime <= Now then - begin - Timer1.Tag := 0; - UpdateTime(); - DisableTimer(); - CountdownDone := True; - Application.Title := APP_NAME; - if DoneAudioEnabled then - PlayAudio(DoneAudio, LoopAudio); - if DoneAppEnabled then - RunApp(DoneApp); - if DoneTrayMsgEnabled then - ShowTrayMessage(DoneTrayMsg); - if DoneMessageEnabled then - ShowDoneMessage(DoneMessage); - - if AutoRestart then - ToggleCountdown(Sender) - else - TrayIconMain.Icon := ImgIconDone.Picture.Icon; - end - else - UpdateTime(); - Timer1.Tag := SecondsBetween(EndTime, Now); - end; -end; - -procedure TMainForm.ShowAbout(Sender: TObject); -var - AboutForm: TAboutForm; -begin - AboutForm := TAboutForm.Create(Self); - AboutForm.ShowModal; - AboutForm.Release; -end; - -procedure TMainForm.OnDestroyForm(Sender: TObject); -begin - if AutoSave then - SaveSettings(Sender); -end; - -procedure TMainForm.OnShowForm(Sender: TObject); -var - //w: integer; - //h: integer; - wRect: TRect; - //cRect: TRect; - flags: integer; - -begin - Self.Position := poDesigned; - WndHandle := GetHandle(); - GetWindowRect(WndHandle, wRect); - flags := SWP_SHOWWINDOW; - - case WndPosition of - Remember: SetWindowPos(WndHandle, HWND_TOP, WndLeft, WndTop, WndWidth, WndHeight, flags); - TopLeft: SetWindowPos(WndHandle, HWND_TOP, 0, 0, WndWidth, WndHeight, flags); - TopRight: SetWindowPos(WndHandle, HWND_TOP, Monitor.WorkareaRect.Right - WndWidth, 0, WndWidth, WndHeight, flags); - BottomLeft: SetWindowPos(WndHandle, HWND_TOP, 0, Monitor.WorkareaRect.Bottom - WndHeight, WndWidth, WndHeight, flags); - BottomRight: SetWindowPos(WndHandle, HWND_TOP, Monitor.WorkareaRect.Right - WndWidth, - Monitor.WorkareaRect.Bottom - WndHeight, WndWidth, WndHeight, flags); - Center: - begin - SetWindowPos(WndHandle, HWND_TOP, 0, 0, WndWidth, WndHeight, SWP_SHOWWINDOW or SWP_NOMOVE); - Self.Position := poScreenCenter; - end; - end; - - // TODO Get this working for window size (getpreferred size gets form, not window) - // Get preferred size before font change, then after and add or subtract that delta from the - // window size? - // AutoSize is working on startup, just not when font size is changed. - // Need to find the method that will set the window to the preferred size, because the preferred size is correct - // setBounds does it, but it also requires - if FontSizeChanged then - begin - //AutoSize:= True; - //MainForm.FontChanged(Sender); - //GetPreferredSize(w, h, True); - //cRect := GetClientRect; - //cRect.Right := w; - //cRect.Bottom := h; - //AdjustClientRect(cRect); - - // These two lines cause an exception - why I don't know - //Count.Width:= w; - //Count.Height:= h; - - //InvalidatePreferredSize; - //Repaint; - //Resize; - //AdjustSize; - - //GetPreferredSize(w, h, True); - //ShowMessageFmt('width: %d, height: %d', [w, h]); - //ShowMessageFmt('width: %d, height: %d', [FormerWidth, FormerHeight]); - //WndWidth:= WndWidth + (w - FormerWidth); - //WndHeight:= WndHeight + (h - FormerHeight); - //SetBounds(Monitor.WorkareaRect.Right - w + rightAdjust, 0, w, h); - //WndWidth:= w; - //WndHeight:= h; - //ShowMessageFmt('width: %d, height: %d', [w, h]); - //exit; - end; - - UpdateAlwaysOnTop(AlwaysOnTop); -end; - -procedure TMainForm.SaveSettings(Sender: TObject); -var - wRect: TRect; -begin - try - IniFile := TIniFile.Create(IniFilename); - IniFile.CacheUpdates := True; - IniFile.WriteInteger(INI_SEC_MAIN, INI_MINUTES, Minutes.Value); - IniFile.WriteBool(INI_SEC_MAIN, INI_ALWAYS_ON_TOP, AlwaysOnTop); - IniFile.WriteBool(INI_SEC_MAIN, INI_MIN_TO_TRAY, MinToTray); - IniFile.WriteBool(INI_SEC_MAIN, INI_AUTOSTART, AutoStart); - IniFile.WriteBool(INI_SEC_MAIN, INI_HIDESECONDS, HideSeconds); - IniFile.WriteBool(INI_SEC_MAIN, INI_CLICKTIME, ClickTime); - IniFile.WriteBool(INI_SEC_MAIN, INI_DBLCLICKTIME, DblClickTime); - IniFile.WriteBool(INI_SEC_MAIN, INI_AUTORESTART, AutoRestart); - IniFile.WriteBool(INI_SEC_MAIN, INI_LOOP_AUDIO, LoopAudio); - IniFile.WriteBool(INI_SEC_MAIN, INI_TICKING_ON, TickingOn); - IniFile.WriteBool(INI_SEC_MAIN, INI_AUTOSAVE, AutoSave); - IniFile.WriteBool(INI_SEC_MAIN, INI_SECONDS_MODE, SecondsMode); - - GetWindowRect(GetHandle(), wRect); - - IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_HEIGHT, wRect.Bottom - wRect.Top); - IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_WIDTH, wRect.Right - wRect.Left); - IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_LEFT, wRect.Left); - IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_TOP, wRect.Top); - IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_POSITION, Ord(WndPosition)); - - IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_MESSAGE, DoneMessage); - IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_MESSAGE_ON, DoneMessageEnabled); - IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE, DoneTrayMsg); - IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE_ON, DoneTrayMsgEnabled); - IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_AUDIO, DoneAudio); - IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_AUDIO_ON, DoneAudioEnabled); - IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_APP, DoneApp); - IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_APP_ON, DoneAppEnabled); - - IniFile.WriteString(INI_SEC_FONTS, INI_FONT_NAME, Count.Font.Name); - IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_CHARSET, Count.Font.CharSet); - IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_COLOR, Count.Font.Color); - IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_SIZE, Count.Font.Size); - IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_STYLE, FontStylesToInt(Count.Font)); - IniFile.WriteInteger(INI_SEC_FONTS, INI_BG_COLOR, Count.Color); - IniFile.UpdateFile; - except - MessageDlg(MSG_WRITE_INI, mtError, [mbOK], 0); - end; -end; - -procedure TMainForm.MinutesChanged(Sender: TObject); -begin - if not Timer1.Enabled then - ResetCountdown(Sender); -end; - -procedure TMainForm.ToggleCompact(Sender: TObject); -begin - SetFieldsVisible(CompactMode); - CompactMode := not CompactMode; -end; - - // TODO Figure out how to remove the fields from the form so they don't - // take up space anymore (and count can expand to top) - // Easier to create a new form, hide this one and show the other one - // Set border style to none too. -procedure TMainForm.SetFieldsVisible(showFields: boolean); -begin - if showFields then - begin - MainForm.Menu := MainMenu1; - end - else - begin - MainForm.Menu := nil; - TimeLabel.Visible := showFields; - Minutes.Visible := showFields; - ImgStart.Visible := showFields; - ImgReset.Visible := showFields; - ImgPause.Visible := showFields; - end; -end; - -procedure TMainForm.CloseApp(Sender: TObject); -begin - OnDestroyForm(Sender); - Close; -end; - -function TMainForm.SecondsToTime(Seconds: integer): string; -var - h, m, s: integer; -begin - h := Seconds div 3600; - m := Seconds div 60 - h * 60; - s := Seconds - (h * 3600 + m * 60); - if HideSeconds then - begin - Result := SysUtils.Format('%.2d:%.2d', [h, m]); - end - else - begin - Result := SysUtils.Format('%.2d:%.2d:%.2d', [h, m, s]); - end; -end; - -procedure TMainForm.FormKeyPress(Sender: TObject; var Key: char); -begin - // TMainForm's KeyPreview property must be set to True for this to work. - if key = #27 then - Close; // 27 = Escape - if (Minutes.Focused) and (key = #13) then - ToggleCountdown(Sender); -end; - - // TODO Animate icon between alarm icon and main so it blinks every second - // Instead of playing icon, animate the clock hands so they go around? - // http://delphi.about.com/od/kbwinshell/l/aa122501a.htm -procedure TMainForm.UpdateTime(); -begin - // Changing caption while it's not visible keeps time from flashing - Count.Visible := False; - Count.Caption := SecondsToTime(Timer1.Tag); - Count.Visible := True; - MenuCount.Caption := Count.Caption; - if Timer1.Enabled then - begin - Application.Title := Count.Caption + ' - ' + APP_NAME; - TrayIconMain.Hint := Count.Caption; - end - else - begin - Application.Title := APP_NAME; - TrayIconMain.Hint := APP_NAME; - end; -end; - -procedure TMainForm.FormWindowStateChange(Sender: TObject); -begin - if not MinToTray then - Exit; - - if WindowState = wsMinimized then - begin - WindowState := wsNormal; - ShowInTaskBar := stNever; - Hide; - end; -end; - -// TODO Interpret env vars in the path -class function TMainForm.GetFilePath(Path: string): string; -begin - if AnsiStartsStr('.', Path) then - begin - Result := ExtractFilePath(Application.ExeName) + Path; - end else - Result := Path; -end; - -procedure TMainForm.PlayAudio(Path: string; Loop: boolean); -begin - if AudioPlaying then - Exit; - if Loop then - begin - AudioPlaying := True; - sndPlaySound(PChar(GetFilePath(Path)), SND_NODEFAULT or SND_ASYNC or SND_LOOP); - end - else - sndPlaySound(PChar(GetFilePath(Path)), SND_NODEFAULT or SND_ASYNC); -end; - -procedure TMainForm.StopAudio(); -begin - sndPlaySound(nil, 0); - AudioPlaying := False; -end; - -procedure TMainForm.PlayTicking(); -begin - sndPlaySound(PChar(GetFilePath(DEF_TICKING_PATH)), SND_NODEFAULT or - SND_ASYNC or SND_LOOP); -end; - -procedure TMainForm.RunApp(Path: string); -begin - ShellExecute(0, 'open', PChar(GetFilePath(Path)), nil, nil, SW_SHOWNORMAL); -end; - -procedure TMainForm.ShowTrayMessage(Msg: string); -begin - with TrayIconMain do - begin - BalloonHint := Msg; - ShowBalloonHint; - BalloonTimeout := 4000; - end; -end; - -procedure TMainForm.ShowDoneMessage(Msg: string); -begin - // http://msdn.microsoft.com/en-us/library/ms645505(VS.85).aspx - Windows.MessageBox(GetHandle(), pChar(msg), 'Done', - MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST or MB_ICONINFORMATION); - StopAudio(); -end; - -function TMainForm.GetHandle(): HWND; -begin - Result := Application.MainForm.Handle; -end; - -procedure TMainForm.SetDefaults(Sender: TObject); -begin - with Sender as TOptionsForm do - BgColor.ButtonColor := DEF_BG_COLOR; - f.Name := DEF_FONT_NAME; - f.Color := DEF_FONT_COLOR; - f.CharSet := DEF_FONT_CHARSET; - f.Size := DEF_FONT_SIZE; - f.Style := IntToFontStyles(DEF_FONT_STYLE); -end; - -function TMainForm.FontStylesToInt(Fnt: TFont): integer; -var - Mask: integer; - Style: TFontStyle; -begin - // Translate the set into a bit mask - Mask := 0; - for Style := Low(TFontStyle) to High(TFontStyle) do - if Style in Fnt.Style then - Mask := Mask or (1 shl Ord(Style)); - Result := Mask; -end; - -function TMainForm.IntToFontStyles(Mask: integer): TFontStyles; -var - i: integer; - StyleSet: TFontStyles; -begin - // Translate the bit mask into a set - StyleSet := []; - for i := 0 to Ord(High(TFontStyle)) do - if Mask and (1 shl i) <> 0 then - StyleSet := StyleSet + [TFontStyle(i)]; - Result := StyleSet; -end; - -procedure TMainForm.UpdateAlwaysOnTop(onTop: boolean); -begin - if onTop then - FormStyle:= fsSystemStayOnTop - else - FormStyle:= fsNormal -end; - -procedure TMainForm.FormActivate(Sender: TObject); -begin - WindowState := wsNormal; - ShowInTaskBar := stDefault; - Show; -end; - -function TMainForm.IsInteger(S: String): boolean; -begin - try - Result := True; - StrToInt(S); - except on E: EConvertError do - Result := False; - end; -end; - -// TODO Only update the time if this value changed (otherwise it resets -// the time every time you modify settings) -// It's not working - test it thoroughly and fix it. -procedure TMainForm.UpdateTimeCaption(newSecondsMode: boolean); -begin - if newSecondsMode <> SecondsMode then - begin - if newSecondsMode then - TimeLabel.Caption := LBL_SECONDS - else - TimeLabel.Caption := LBL_MINUTES; - SetTimer(); - end; - -end; - -initialization - {$I mainform1.lrs} -end. - diff --git a/model/config.pas b/model/config.pas new file mode 100644 index 0000000..2710348 --- /dev/null +++ b/model/config.pas @@ -0,0 +1,388 @@ +unit Config; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, Graphics; + +type + TPosition = (Center, Remember, TopLeft, TopRight, BottomLeft, BottomRight); + + TFontConfig = class + Name: String; + Charset: Integer; + Color: Integer; + BgColor: Integer; + Size: Integer; + Style: TFontStyles; + end; + + TConfig = class + private + // Main + FMinutes: Integer; + FAlwaysOnTop: boolean; + FMinToTray: boolean; + FAutoStart: boolean; + FHideSeconds: boolean; + FClickTime: boolean; + FDblClickTime: boolean; + FAutoRestart: boolean; + FLoopAudio: boolean; + FTickingOn: boolean; + FAutoSave: boolean; + FSecondsMode: boolean; + FCompactMode: boolean; + // Placement + FWndHeight: integer; + FWndWidth: integer; + FWndLeft: integer; + FWndTop: integer; + FWndPosition: TPosition; + // Alarms + FDoneMessage: string; + FDoneMessageEnabled: boolean; + FDoneTrayMsg: string; + FDoneTrayMsgEnabled: boolean; + FDoneAudio: string; + FDoneAudioEnabled: boolean; + FDoneApp: string; + FDoneAppEnabled: boolean; + // Font + FFont: TFontConfig; + FDefaultFont: TFontConfig; + + procedure SetDoneMessage(Msg: String); + procedure SetDoneTrayMessage(Msg: String); + public + constructor Create; + destructor Free; + function Load : Boolean; + function Save : Boolean; + function GetDefaultFont : TFontConfig; + + // Main + property Minutes: Integer read FMinutes write FMinutes; + property AlwaysOnTop: boolean read FAlwaysOnTop write FAlwaysOnTop; + property MinToTray: boolean read FMinToTray write FMinToTray; + property AutoStart: boolean read FAutoStart write FAutoStart; + property HideSeconds: boolean read FHideSeconds write FHideSeconds; + property ClickTime: boolean read FClickTime write FClickTime; + property DblClickTime: boolean read FDblClickTime write FDblClickTime; + property AutoRestart: boolean read FAutoRestart write FAutoRestart; + property LoopAudio: boolean read FLoopAudio write FLoopAudio; + property TickingOn: boolean read FTickingOn write FTickingOn; + property AutoSave: boolean read FAutoSave write FAutoSave; + property SecondsMode: boolean read FSecondsMode write FSecondsMode; // Undocumented mode to treat minutes as seconds + property CompactMode: boolean read FCompactMode write FCompactMode; // not implemented + // Placement + property WndHeight: integer read FWndHeight write FWndHeight; + property WndWidth: integer read FWndWidth write FWndWidth; + property WndLeft: integer read FWndLeft write FWndLeft; + property WndTop: integer read FWndTop write FWndTop; + property WndPosition: TPosition read FWndPosition write FWndPosition; + // Alarm + property DoneMessage: string read FDoneMessage write SetDoneMessage; + property DoneMessageEnabled: boolean read FDoneMessageEnabled write FDoneMessageEnabled; + property DoneTrayMsg: string read FDoneTrayMsg write SetDoneTrayMessage; + property DoneTrayMsgEnabled: boolean read FDoneTrayMsgEnabled write FDoneTrayMsgEnabled; + property DoneAudio: string read FDoneAudio write FDoneAudio; + property DoneAudioEnabled: boolean read FDoneAudioEnabled write FDoneAudioEnabled; + property DoneApp: string read FDoneApp write FDoneApp; + property DoneAppEnabled: boolean read FDoneAppEnabled write FDoneAppEnabled; + // Fonts + property Font: TFontConfig read FFont write FFont; +end; + +function GetConfig : TConfig; + +implementation + +uses + IniFiles; + +// Default values +const + INI_SEC_MAIN = 'Main'; + INI_SEC_ALARMS = 'Alarms'; + INI_SEC_PLACEMENT = 'Placement'; + INI_SEC_FONTS = 'Fonts'; + INI_MINUTES = 'Minutes'; + INI_ALWAYS_ON_TOP = 'AlwaysOnTop'; + INI_MIN_TO_TRAY = 'MinToTray'; + INI_AUTOSTART = 'AutoStart'; + INI_HIDESECONDS = 'HideSeconds'; + INI_CLICKTIME = 'ClickTime'; + INI_DBLCLICKTIME = 'DoubleClickTime'; + INI_AUTORESTART = 'AutoRestart'; + INI_LOOP_AUDIO = 'LoopAudio'; + INI_TICKING_ON = 'TickingEnabled'; + INI_AUTOSAVE = 'AutoSave'; + INI_HEIGHT = 'WinHeight'; + INI_WIDTH = 'WinWidth'; + INI_LEFT = 'WinLeft'; + INI_TOP = 'WinTop'; + INI_POSITION = 'WinPosition'; + INI_DONE_MESSAGE = 'Message'; + INI_DONE_MESSAGE_ON = 'MessageEnabled'; + INI_DONE_TRAY_MESSAGE = 'TrayMessage'; + INI_DONE_TRAY_MESSAGE_ON = 'TrayMessageEnabled'; + INI_DONE_AUDIO = 'AudioFile'; + INI_DONE_AUDIO_ON = 'AudioFileEnabled'; + INI_DONE_APP = 'RunApp'; + INI_DONE_APP_ON = 'RunAppEnabled'; + INI_SECONDS_MODE = 'SecondsMode'; + INI_FONT_NAME = 'Name'; + INI_FONT_CHARSET = 'Charset'; + INI_FONT_COLOR = 'Color'; + INI_FONT_SIZE = 'Size'; + INI_FONT_STYLE = 'Style'; + INI_BG_COLOR = 'BgColor'; + + INI_FILE_NAME = 'SnapTimer.ini'; + DEF_TIME = 15; + DEF_DONE_MSG = 'Time''s up'; + DEF_DONE_MSG_ON = True; + DEF_DONE_TRAY_MSG = 'Countdown completed'; + DEF_DONE_TRAY_MSG_ON = False; + DEF_DONE_AUDIO = '.\sounds\alarm_clock_bell.wav'; + DEF_DONE_AUDIO_ON = False; + DEF_DONE_APP = ''; + DEF_DONE_APP_ON = False; + DEF_ALWAYS_ON_TOP = False; + DEF_MIN_TO_TRAY = False; + DEF_AUTOSTART = False; + DEF_HIDESECONDS = False; + DEF_CLICKTIME = True; + DEF_DBLCLICKTIME = True; + DEF_AUTORESTART = False; + DEF_LOOP_AUDIO = False; + DEF_TICKING_ON = False; + DEF_TICKING_PATH = '.\sounds\ticking\ticking.wav'; + DEF_AUTOSAVE = True; + DEF_SECONDS_MODE = False; + DEF_POSITION = Ord(Center); + DEF_HEIGHT = 149; + DEF_WIDTH = 214; + DEF_FONT_NAME = 'Arial'; + DEF_FONT_CHARSET = 0; + DEF_FONT_COLOR = clNavy; + DEF_FONT_BG_COLOR = clNone; + DEF_FONT_SIZE = 38; + DEF_FONT_STYLE = 0; + + var + ConfigInstance : TConfig; + + + function FontStylesToInt(FontStyles: TFontStyles): integer; + var + Mask: integer; + Style: TFontStyle; + begin + // Translate the set into a bit mask + Mask := 0; + for Style := Low(TFontStyle) to High(TFontStyle) do + if Style in FontStyles then + Mask := Mask or (1 shl Ord(Style)); + Result := Mask; + end; + + function IntToFontStyles(Mask: integer): TFontStyles; + var + i: integer; + StyleSet: TFontStyles; + begin + // Translate the bit mask into a set + StyleSet := []; + for i := 0 to Ord(High(TFontStyle)) do + if Mask and (1 shl i) <> 0 then + StyleSet := StyleSet + [TFontStyle(i)]; + Result := StyleSet; + end; + +procedure TConfig.SetDoneMessage(Msg: String); +begin + // TODO Set some reasonable length limit + FDoneMessage:= Msg; +end; + +procedure TConfig.SetDoneTrayMessage(Msg: String); +begin + // TODO Set some reasonable length limit + FDoneTrayMsg:= Msg; +end; + + +constructor TConfig.Create; +begin + // Main + Minutes:= DEF_TIME; + AlwaysOnTop := DEF_ALWAYS_ON_TOP; + MinToTray:= DEF_MIN_TO_TRAY; + AutoStart:= DEF_AUTOSTART; + HideSeconds:= DEF_HIDESECONDS; + ClickTime:= DEF_CLICKTIME; + DblClickTime:= DEF_DBLCLICKTIME; + AutoRestart:= DEF_AUTORESTART; + LoopAudio:= DEF_LOOP_AUDIO; + TickingOn:= DEF_TICKING_ON; + AutoSave:= DEF_AUTOSAVE; + SecondsMode:= DEF_SECONDS_MODE; + CompactMode := False; + // Placement + WndPosition:= TPosition(DEF_POSITION); + WndWidth:= DEF_WIDTH; + WndHeight:= DEF_HEIGHT; + // Alarms + DoneMessage:= DEF_DONE_MSG; + DoneMessageEnabled:= DEF_DONE_MSG_ON; + DoneTrayMsg:= DEF_DONE_TRAY_MSG; + DoneTrayMsgEnabled:= DEF_DONE_TRAY_MSG_ON; + DoneAudio:= DEF_DONE_AUDIO; + DoneAudioEnabled:= DEF_DONE_AUDIO_ON; + DoneApp:= DEF_DONE_APP; + DoneAppEnabled:= DEF_DONE_APP_ON; + // Fonts + FFont:= TFontConfig.Create; + Font.Name:= DEF_FONT_NAME; + Font.Charset:= DEF_FONT_CHARSET; + Font.Color:= DEF_FONT_COLOR; + Font.BgColor:= DEF_FONT_BG_COLOR; + Font.Size:= DEF_FONT_SIZE; + Font.Style:= IntToFontStyles(DEF_FONT_STYLE); + + FDefaultFont:= TFontConfig.Create; + FDefaultFont.Name:= DEF_FONT_NAME; + FDefaultFont.Charset:= DEF_FONT_CHARSET; + FDefaultFont.Color:= DEF_FONT_COLOR; + FDefaultFont.BgColor:= DEF_FONT_BG_COLOR; + FDefaultFont.Size:= DEF_FONT_SIZE; + FDefaultFont.Style:= IntToFontStyles(DEF_FONT_STYLE); +end; + +destructor TConfig.Free; +begin + FFont.Free; + FDefaultFont.Free; +end; + +function TConfig.Load : Boolean; +var IniFile: TIniFile; +begin + try + IniFile := TIniFile.Create(INI_FILE_NAME); + // Main + Minutes := IniFile.ReadInteger(INI_SEC_MAIN, INI_MINUTES, DEF_TIME); + AlwaysOnTop := IniFile.ReadBool(INI_SEC_MAIN, INI_ALWAYS_ON_TOP, DEF_ALWAYS_ON_TOP); + MinToTray := IniFile.ReadBool(INI_SEC_MAIN, INI_MIN_TO_TRAY, DEF_MIN_TO_TRAY); + AutoStart := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTOSTART, DEF_AUTOSTART); + HideSeconds := IniFile.ReadBool(INI_SEC_MAIN, INI_HIDESECONDS, DEF_HIDESECONDS); + ClickTime := IniFile.ReadBool(INI_SEC_MAIN, INI_CLICKTIME, DEF_CLICKTIME); + DblClickTime := IniFile.ReadBool(INI_SEC_MAIN, INI_DBLCLICKTIME, DEF_DBLCLICKTIME); + AutoRestart := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTORESTART, DEF_AUTORESTART); + LoopAudio := IniFile.ReadBool(INI_SEC_MAIN, INI_LOOP_AUDIO, DEF_LOOP_AUDIO); + TickingOn := IniFile.ReadBool(INI_SEC_MAIN, INI_TICKING_ON, DEF_TICKING_ON); + AutoSave := IniFile.ReadBool(INI_SEC_MAIN, INI_AUTOSAVE, DEF_AUTOSAVE); + SecondsMode := IniFile.ReadBool(INI_SEC_MAIN, INI_SECONDS_MODE, DEF_SECONDS_MODE); + // Placement + WndHeight := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_HEIGHT, DEF_HEIGHT); + WndWidth := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_WIDTH, DEF_WIDTH); + WndLeft := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_LEFT, 0); + WndTop := IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_TOP, 0); + WndPosition := TPosition(IniFile.ReadInteger(INI_SEC_PLACEMENT, INI_POSITION, DEF_POSITION)); + // Alarm + DoneMessage := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_MESSAGE, DEF_DONE_MSG); + DoneMessageEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_MESSAGE_ON, DEF_DONE_MSG_ON); + DoneTrayMsg := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE, DEF_DONE_TRAY_MSG); + DoneTrayMsgEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE_ON, DEF_DONE_TRAY_MSG_ON); + DoneAudio := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_AUDIO, DEF_DONE_AUDIO); + DoneAudioEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_AUDIO_ON, DEF_DONE_AUDIO_ON); + DoneApp := IniFile.ReadString(INI_SEC_ALARMS, INI_DONE_APP, DEF_DONE_APP); + DoneAppEnabled := IniFile.ReadBool(INI_SEC_ALARMS, INI_DONE_APP_ON, DEF_DONE_APP_ON); + // Fonts + Font.Name := IniFile.ReadString(INI_SEC_FONTS, INI_FONT_NAME, DEF_FONT_NAME); + Font.Charset := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_CHARSET, DEF_FONT_CHARSET); + Font.Color := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_COLOR, DEF_FONT_COLOR); + Font.BgColor := IniFile.ReadInteger(INI_SEC_FONTS, INI_BG_COLOR, DEF_FONT_BG_COLOR); + Font.Size := IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_SIZE, DEF_FONT_SIZE); + Font.Style := IntToFontStyles(IniFile.ReadInteger(INI_SEC_FONTS, INI_FONT_STYLE, DEF_FONT_STYLE)); + except + Result:= False; + end; + Result:= True; + + if Assigned(IniFile) then + IniFile.Free; +end; + +function TConfig.Save : Boolean; +var IniFile: TIniFile; +begin + try + IniFile := TIniFile.Create(INI_FILE_NAME); + IniFile.CacheUpdates := True; + // Main + IniFile.WriteInteger(INI_SEC_MAIN, INI_MINUTES, Minutes); + IniFile.WriteBool(INI_SEC_MAIN, INI_ALWAYS_ON_TOP, AlwaysOnTop); + IniFile.WriteBool(INI_SEC_MAIN, INI_MIN_TO_TRAY, MinToTray); + IniFile.WriteBool(INI_SEC_MAIN, INI_AUTOSTART, AutoStart); + IniFile.WriteBool(INI_SEC_MAIN, INI_HIDESECONDS, HideSeconds); + IniFile.WriteBool(INI_SEC_MAIN, INI_CLICKTIME, ClickTime); + IniFile.WriteBool(INI_SEC_MAIN, INI_DBLCLICKTIME, DblClickTime); + IniFile.WriteBool(INI_SEC_MAIN, INI_AUTORESTART, AutoRestart); + IniFile.WriteBool(INI_SEC_MAIN, INI_LOOP_AUDIO, LoopAudio); + IniFile.WriteBool(INI_SEC_MAIN, INI_TICKING_ON, TickingOn); + IniFile.WriteBool(INI_SEC_MAIN, INI_AUTOSAVE, AutoSave); + IniFile.WriteBool(INI_SEC_MAIN, INI_SECONDS_MODE, SecondsMode); + // Placement + IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_HEIGHT, WndHeight); + IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_WIDTH, WndWidth); + IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_LEFT, WndLeft); + IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_TOP, WndTop); + IniFile.WriteInteger(INI_SEC_PLACEMENT, INI_POSITION, Ord(WndPosition)); + // Alarms + IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_MESSAGE, DoneMessage); + IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_MESSAGE_ON, DoneMessageEnabled); + IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE, DoneTrayMsg); + IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_TRAY_MESSAGE_ON, DoneTrayMsgEnabled); + IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_AUDIO, DoneAudio); + IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_AUDIO_ON, DoneAudioEnabled); + IniFile.WriteString(INI_SEC_ALARMS, INI_DONE_APP, DoneApp); + IniFile.WriteBool(INI_SEC_ALARMS, INI_DONE_APP_ON, DoneAppEnabled); + // Fonts + IniFile.WriteString(INI_SEC_FONTS, INI_FONT_NAME, Font.Name); + IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_CHARSET, Font.Charset); + IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_COLOR, Font.Color); + IniFile.WriteInteger(INI_SEC_FONTS, INI_BG_COLOR, Font.BgColor); + IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_SIZE, Font.Size); + IniFile.WriteInteger(INI_SEC_FONTS, INI_FONT_STYLE, FontStylesToInt(Font.Style)); + + IniFile.UpdateFile; + except + Result:= False; + end; + Result:= True; + + if Assigned(IniFile) then + IniFile.Free; +end; + +function TConfig.GetDefaultFont : TFontConfig; +begin + Result:= FDefaultFont; +end; + + +function GetConfig : TConfig; +begin + Result:= ConfigInstance; +end; + +initialization + ConfigInstance:= TConfig.Create; + +end. + diff --git a/model/mytimer.pas b/model/mytimer.pas new file mode 100644 index 0000000..a523802 --- /dev/null +++ b/model/mytimer.pas @@ -0,0 +1,205 @@ +unit mytimer; + +{$mode objfpc} + +interface + +uses + Classes, SysUtils, ExtCtrls; + +type + TMode = (Timer, Stopwatch); + TState = (Ready, Running, Finished, Paused); + + // TODO find better name + TMyTimer = class + private + FTimer: TTimer; + // Start or End time, depending on the Mode. + FTime: TDateTime; + FSeconds: Integer; + FSecondsInitial: Integer; + FState: TState; + FMode: TMode; + FOnSecondElapsed: TNotifyEvent; + FOnStateChanged: TNotifyEvent; + + function GetSeconds : Integer; + procedure SetSeconds(AValue: Integer); + function GetMinutes : Integer; + procedure SetMinutes(AValue: Integer); + + procedure OnTimer(Sender: TObject); + procedure UpdateSeconds; + procedure UpdateTime; + public + constructor Create; + destructor Free; + procedure Start; + procedure Pause; + procedure Resume; + procedure Toggle; + procedure Reset; + + property Seconds: Integer read GetSeconds write SetSeconds; + property Minutes: Integer read GetMinutes write SetMinutes; + property State: TState read FState; + property Mode: TMode read FMode write FMode; + property OnSecondElapsed: TNotifyEvent read FOnSecondElapsed write FOnSecondElapsed; + property OnStateChanged: TNotifyEvent read FOnStateChanged write FOnStateChanged; + private + +end; + +implementation + +uses DateUtils; + +function TMyTimer.GetSeconds : Integer; +begin + Result:= FSeconds; +end; + +procedure TMyTimer.SetSeconds(AValue: Integer); +begin + if State <> Ready then + Raise Exception.Create('Timer is not in `Ready` state'); + FSeconds:= AValue; + FSecondsInitial:= AValue; +end; + +function TMyTimer.GetMinutes : Integer; +begin + Result:= FSeconds div 60; +end; + +procedure TMyTimer.SetMinutes(AValue: Integer); +begin + if State <> Ready then + Raise Exception.Create('Timer is not in `Ready` or `Finished` state'); + FSeconds:= AValue * 60; + FSecondsInitial:= FSeconds; +end; + +procedure TMyTimer.OnTimer(Sender: TObject); +begin + if Mode = Timer then + begin + if Now >= FTime then + begin + FTimer.Enabled:= False; + FSeconds:= 0; + FState:= Finished; + if Assigned(FOnStateChanged) then + FOnStateChanged(self); + end + else + UpdateSeconds; + end + else + begin + // Stopwatch + UpdateSeconds; + end +end; + +procedure TMyTimer.UpdateSeconds; +var s : Integer; +begin + s:= SecondsBetween(FTime, Now); + if s <> FSeconds then + begin + FSeconds:= s; + if Assigned(FOnSecondElapsed) then + FOnSecondElapsed(self); + end; +end; + +procedure TMyTimer.UpdateTime; +begin + if Mode = Timer then + FTime:= Now + (FSeconds * OneSecond) // FTime is the end time + else + FTime:= Now - (FSeconds * OneSecond) // FTime is the start time +end; + +constructor TMyTimer.Create; +begin + FTimer:= TTimer.Create(nil); + FTimer.Enabled:= False; + FTimer.Interval:= 160; + FTimer.OnTimer:= @OnTimer; + FSeconds:= 10 * 60; + FSecondsInitial:= FSeconds; + FState:= Ready; + FMode:= Timer; + FOnSecondElapsed:= nil; + FOnStateChanged:= nil; +end; + +destructor TMyTimer.Free; +begin + FTimer.Free; +end; + + +procedure TMyTimer.Start; +begin + if State <> Ready then + Raise Exception.Create('Timer is not in `Ready` state'); + + FState:= Running; + UpdateTime; + if Assigned(FOnStateChanged) then + FOnStateChanged(self); + FTimer.Enabled:= True; +end; + +procedure TMyTimer.Pause; +begin + if State <> Running then + Raise Exception.Create('Timer is not in `Running` state'); + + FState:= Paused; + if Assigned(FOnStateChanged) then + FOnStateChanged(self); + FTimer.Enabled:= False; +end; + +procedure TMyTimer.Resume; +begin + if State <> Paused then + Raise Exception.Create('Timer is not in `Paused` state'); + + FState:= Running; + UpdateTime; + if Assigned(FOnStateChanged) then + FOnStateChanged(self); + FTimer.Enabled:= True; +end; + +procedure TMyTimer.Toggle; +begin + case State of + Ready : Start; + Running : Pause; + Finished : Reset; + Paused : Resume; + end; +end; + +procedure TMyTimer.Reset; +var PrevState: TState; +begin + PrevState:= FState; + FTimer.Enabled:= False; + FState:= Ready; + FSeconds:= FSecondsInitial; + // We want FOnStateChanged to be called even if we are in `Ready` state. + if Assigned(FOnStateChanged) {and (PrevState <> Ready)} then + FOnStateChanged(self); +end; + + +end. + diff --git a/model/utils.pas b/model/utils.pas new file mode 100644 index 0000000..1a0812d --- /dev/null +++ b/model/utils.pas @@ -0,0 +1,81 @@ +unit utils; + +{$mode objfpc}{$H+} + +interface + +// TUtils class serves as a namespace. +type + TUtils = class + class function SecondsToTime(Seconds: integer; HideSeconds: Boolean): string; + class function GetFilePath(Path: string): string; + class procedure RunApp(Path: string); + class function IsInteger(S: String): boolean; + class procedure PlayAudio(Path: string; Loop: boolean); + class procedure StopAudio; + end; + +implementation + +uses + Classes, SysUtils, StrUtils, Forms, Windows, MMSystem; + +class function TUtils.SecondsToTime(Seconds: integer; HideSeconds: Boolean): string; +var + h, m, s: integer; +begin + h := Seconds div 3600; + m := Seconds div 60 - h * 60; + s := Seconds - (h * 3600 + m * 60); + if HideSeconds then + begin + Result := SysUtils.Format('%.2d:%.2d', [h, m]); + end + else + begin + Result := SysUtils.Format('%.2d:%.2d:%.2d', [h, m, s]); + end; +end; + +// TODO Interpret env vars in the path +class function TUtils.GetFilePath(Path: string): string; +begin + if AnsiStartsStr('.', Path) then + begin + Result := ExtractFilePath(Application.ExeName) + Path; + end else + Result := Path; +end; + +class procedure TUtils.RunApp(Path: string); +begin + ShellExecute(0, 'open', PChar(GetFilePath(Path)), nil, nil, SW_SHOWNORMAL); +end; + +class function TUtils.IsInteger(S: String): boolean; +begin + try + Result := True; + StrToInt(S); + except on E: EConvertError do + Result := False; + end; +end; + + +class procedure TUtils.PlayAudio(Path: string; Loop: boolean); +begin + if Loop then + sndPlaySound(PChar(GetFilePath(Path)), SND_NODEFAULT or SND_ASYNC or SND_LOOP) + else + sndPlaySound(PChar(GetFilePath(Path)), SND_NODEFAULT or SND_ASYNC); +end; + +class procedure TUtils.StopAudio; +begin + sndPlaySound(nil, 0); +end; + + +end. + diff --git a/settings.pas b/settings.pas deleted file mode 100644 index d1ed05a..0000000 --- a/settings.pas +++ /dev/null @@ -1,230 +0,0 @@ -unit settings; - -{$mode objfpc}{$H+} - -interface - -uses - SysUtils, LResources, Forms, Graphics, Dialogs, StdCtrls, ComCtrls, Spin; - -type - - { TOptionsForm } - - TOptionsForm = class(TForm) - BtnOk: TButton; - BtnCancel: TButton; - BtnDefaults: TButton; - CheckAutoRestart: TCheckBox; - CheckHideSeconds: TCheckBox; - CheckSecondsMode: TCheckBox; - CheckAutoSave: TCheckBox; - CheckAlwaysOnTop: TCheckBox; - CheckTicking: TCheckBox; - CheckLoopAudio: TCheckBox; - CheckClickTime: TCheckBox; - CheckDblClickTime: TCheckBox; - FontColor: TColorButton; - FontNameBtn: TButton; - CheckAutostart: TCheckBox; - CheckMinToTray: TCheckBox; - BgColor: TColorButton; - FontSize: TSpinEdit; - Label1: TLabel; - FontName: TStaticText; - LabelTextColor: TLabel; - LabelTextColor1: TLabel; - TimerFontBox: TGroupBox; - PositionCombo: TComboBox; - GroupBox1: TGroupBox; - GroupBox2: TGroupBox; - NotifyAudio: TEdit; - NotifyAudioBtn: TButton; - NotifyAudioOn: TCheckBox; - NotifyAudioTest: TButton; - NotifyMsg: TEdit; - NotifyMsgOn: TCheckBox; - NotifyMsgTest: TButton; - NotifyRunApp: TEdit; - NotifyRunAppOn: TCheckBox; - NotifyRunBtn: TButton; - NotifyRunTest: TButton; - NotifyTrayMsg: TEdit; - NotifyTrayMsgOn: TCheckBox; - NotifyTrayMsgTest: TButton; - PageControl1: TPageControl; - GeneralTab: TTabSheet; - AlarmsTab: TTabSheet; - FontsTab: TTabSheet; - procedure ChooseFont(Sender: TObject); - procedure FormKeyPress(Sender: TObject; var Key: char); - procedure GetAudioFile(Sender: TObject); - procedure GetAppFile(Sender: TObject); - function GetFile(Orig: String; FilterStr: String; Dir : String): String; - procedure EnableFields(Sender: TObject); - procedure SetDefaults(Sender: TObject); - procedure TestAudio(Sender: TObject); - procedure TestMessage(Sender: TObject); - procedure TestRunApp(Sender: TObject); - procedure TestTrayMsg(Sender: TObject); - procedure UpdateFontColor(Sender: TObject); - procedure UpdateFonts(Sender: TObject); - procedure UpdateFontSize(Sender: TObject); - private - { private declarations } - public - { public declarations } - end; - -var - OptionsForm: TOptionsForm; - f: TFont; - -implementation -uses MainForm1; -{ TOptionsForm } - -// TODO Move all text strings to the top - -procedure TOptionsForm.GetAudioFile(Sender: TObject); -begin - NotifyAudio.Text := GetFile(NotifyAudio.Text, 'Wave files (*.wav)|*.wav', GetCurrentDir + '\sounds'); -end; - -procedure TOptionsForm.FormKeyPress(Sender: TObject; var Key: char); -begin - if key = #27 then Close; -end; - -procedure TOptionsForm.ChooseFont(Sender: TObject); -var - dlg : TFontDialog; -begin - dlg := TFontDialog.Create(nil); - dlg.Font := f; - - if dlg.Execute then - begin - f.Name := dlg.Font.Name; - f.Size := dlg.Font.Size; - // TODO Only set color if user changed it (XP dialog doesn't support custom colors) - // Works fine on Win7 - //if not (f.Color = dlg.Font.Color) then f.Color:= dlg.Font.Color; - //f.Color:= dlg.Font.Color; - f.CharSet:= dlg.Font.CharSet; - f.Style:= dlg.Font.Style; - UpdateFonts(Sender); - end; - dlg.Free; -end; - -procedure TOptionsForm.UpdateFonts(Sender: TObject); -begin - FontName.Caption := f.Name + ' : 1234567890'; - FontName.Font.Name := f.Name; - FontName.Font.Color := f.Color; - FontName.Font.CharSet := f.CharSet; - FontName.Font.Style := f.Style; - FontName.Font.Quality:= fqAntialiased; - FontName.Font.Size := 18; - - FontName.Color := BgColor.ButtonColor; - FontSize.Value := f.Size; - FontColor.ButtonColor := f.Color; -end; - -procedure TOptionsForm.UpdateFontSize(Sender: TObject); -begin - f.Size := FontSize.Value; - UpdateFonts(Sender); -end; - -procedure TOptionsForm.UpdateFontColor(Sender: TObject); -begin - f.Color := FontColor.ButtonColor; - UpdateFonts(Sender); -end; - -procedure TOptionsForm.GetAppFile(Sender: TObject); -begin - NotifyRunApp.Text := GetFile(NotifyRunApp.Text, 'Executables (*.exe)|*.exe|All files (*.*)|*.*', GetCurrentDir); -end; - -function TOptionsForm.GetFile(Orig: String; FilterStr: String; Dir : String): String; -var - dlg : TOpenDialog; - fname : String; - cwd : String; - relPath : String; -begin - dlg := TOpenDialog.Create(nil); - dlg.FileName := ''; - if DirectoryExists(Dir) - then dlg.InitialDir := Dir - else dlg.InitialDir := GetCurrentDir; - dlg.Options := [ofFileMustExist]; - dlg.Filter := FilterStr; - dlg.FilterIndex := 1; - - if dlg.Execute then - begin - fname := dlg.FileName; - cwd := GetCurrentDir + '\'; - relPath := ExtractRelativePath(cwd, ExtractFilePath(fname)); - Result := '.\' + relPath + ExtractFileName(fname); - end else begin - Result := Orig; - end; - dlg.Free; -end; - -procedure TOptionsForm.EnableFields(Sender: TObject); -begin - NotifyMsg.Enabled := NotifyMsgOn.Checked; - NotifyMsgTest.Enabled := NotifyMsgOn.Checked; - NotifyTrayMsg.Enabled := NotifyTrayMsgOn.Checked; - NotifyTrayMsgTest.Enabled := NotifyTrayMsgOn.Checked; - - NotifyAudio.Enabled := NotifyAudioOn.Checked; - NotifyAudioBtn.Enabled := NotifyAudioOn.Checked; - NotifyAudioTest.Enabled := NotifyAudioOn.Checked; - - NotifyRunApp.Enabled := NotifyRunAppOn.Checked; - NotifyRunBtn.Enabled := NotifyRunAppOn.Checked; - NotifyRunTest.Enabled := NotifyRunAppOn.Checked; -end; - -procedure TOptionsForm.SetDefaults(Sender: TObject); -begin - (Self.Owner as TMainForm).SetDefaults(Self); - UpdateFonts(Sender); -end; - -procedure TOptionsForm.TestAudio(Sender: TObject); -begin -with Self.Owner as TMainForm do - PlayAudio(NotifyAudio.Text, False); -end; - -procedure TOptionsForm.TestMessage(Sender: TObject); -begin -with Self.Owner as TMainForm do - ShowDoneMessage(NotifyMsg.Text); -end; - -procedure TOptionsForm.TestRunApp(Sender: TObject); -begin -with Self.Owner as TMainForm do - RunApp(NotifyRunApp.Text); -end; - -procedure TOptionsForm.TestTrayMsg(Sender: TObject); -begin -with Self.Owner as TMainForm do - ShowTrayMessage(NotifyTrayMsg.Text); -end; - -initialization - {$I settings.lrs} -end. - diff --git a/snaptimer.lpi b/snaptimer.lpi index ce79283..9981d7c 100644 --- a/snaptimer.lpi +++ b/snaptimer.lpi @@ -26,6 +26,7 @@ + @@ -84,34 +85,51 @@ - + - + - - - + - + - - - - + - + + + + + + + + + + + + + + + + + + + + + + + @@ -122,6 +140,7 @@ + diff --git a/snaptimer.lpr b/snaptimer.lpr index 3f45b9a..22de19c 100644 --- a/snaptimer.lpr +++ b/snaptimer.lpr @@ -3,7 +3,7 @@ {$mode objfpc}{$H+} uses - Forms, Interfaces, MainForm1, About, settings; + Forms, Interfaces, MainForm1, utils; {$R *.res} diff --git a/snaptimer.lps b/snaptimer.lps new file mode 100644 index 0000000..6dfeb25 --- /dev/null +++ b/snaptimer.lps @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/snaptimer.lrs b/snaptimer.lrs deleted file mode 100644 index f371f79..0000000 --- a/snaptimer.lrs +++ /dev/null @@ -1,374 +0,0 @@ -LazarusResources.Add('MAINICON','ICO',[ - #0#0#1#0#4#0' '#0#0#1#0#8#0#168#8#0#0'F'#0#0#0#16#16#0#0#1#0#8#0'h'#5#0#0#238 - +#8#0#0' '#0#0#1#0' '#0#168#16#0#0'V'#14#0#0#16#16#0#0#1#0' '#0'h'#4#0#0#254 - +#30#0#0'('#0#0#0' '#0#0#0'@'#0#0#0#1#0#8#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0'711'#0'<44'#0'A88'#0'C=;'#0'H?='#0'NFC'#0'OGE'#0'ULJ' - +#0'\RO'#0'nxz'#0'p{}'#0#175'k'#19#0#175'l'#19#0#175'l'#20#0#176'm'#22#0#177 - +'o'#24#0#178'p'#25#0#177'q'#29#0#180'q'#28#0#183'u!'#0#186'w$'#0#185'x%'#0 - +#177'v*'#0#178'x+'#0#181'y,'#0#188'z)'#0#184'|/'#0#190#127'.'#0#208#130','#0 - +#193#129'3'#0#196#128'2'#0#195#132'7'#0#198#132'6'#0#203#136';'#0#201#136'<' - +#0#204#136'<'#0#214#141':'#0#217#145'='#0#177#134'M'#0#183#140'S'#0#179#156 - +'~'#0#202#139'D'#0#205#141'D'#0#209#141'B'#0#209#143'G'#0#207#145'L'#0#218 - +#145'B'#0#218#148'A'#0#218#147'D'#0#210#145'I'#0#213#145'I'#0#211#147'M'#0 - +#217#151'N'#0#221#153'I'#0#219#155'M'#0#220#155'M'#0#221#156'N'#0#192#149'\' - +#0#212#153'T'#0#219#154'Q'#0#220#157'Q'#0#223#158'U'#0#209#155'Z'#0#217#158 - +'Z'#0#220#156'Y'#0#224#158'M'#0#224#155'Q'#0#227#159'W'#0#225#158'X'#0#222 - +#161'U'#0#226#162'Q'#0#225#167'Z'#0#224#161'^'#0#199#155'a'#0#197#158'j'#0 - +#219#163'b'#0#220#163'b'#0#222#167'g'#0#198#166'z'#0#230#165'a'#0#227#170'l' - +#0#228#171'm'#0#231#172'o'#0#234#173'l'#0#232#174'r'#0#237#176'q'#0#233#176 - +'v'#0#235#178'y'#0#237#183'~'#0#240#181'z'#0'm~'#129#0'n'#128#130#0't'#128 - +#128#0'r'#131#134#0'u'#131#133#0'q'#132#135#0'x'#132#133#0'q'#133#136#0'z' - +#134#136#0'}'#137#138#0#130#138#139#0#129#139#141#0#129#141#142#0#133#141#142 - +#0#131#144#145#0#132#144#146#0#133#147#148#0#137#145#144#0#142#151#152#0#148 - +#157#156#0#189#165#135#0#178#174#153#0#153#162#161#0#181#182#167#0#207#176 - +#130#0#238#184#128#0#242#186#130#0#216#197#156#0#242#193#141#0#249#198#146#0 - +#252#201#150#0#220#207#171#0#227#206#163#0#255#213#168#0#226#211#176#0#164 - +#191#200#0#154#204#235#0#153#205#236#0#172#211#230#0#166#211#233#0#162#210 - +#238#0#170#218#234#0#174#221#240#0#176#222#245#0#175#224#238#0#178#228#240#0 - +#182#228#241#0#182#231#244#0#184#231#244#0#183#232#241#0#183#232#245#0#185 - +#232#241#0#188#234#241#0#190#236#243#0#186#234#244#0#187#236#245#0#190#237 - +#246#0#185#228#250#0#191#236#249#0#190#242#253#0#210#213#196#0#196#217#216#0 - +#213#228#216#0#222#241#235#0#192#235#242#0#194#236#243#0#193#237#245#0#197 - +#237#244#0#196#239#248#0#195#240#247#0#198#241#247#0#200#240#245#0#205#241 - +#246#0#193#240#249#0#197#241#248#0#196#240#255#0#197#247#255#0#201#242#249#0 - +#206#243#248#0#202#244#249#0#205#245#250#0#207#245#252#0#202#249#255#0#205 - +#250#254#0#207#252#255#0#210#244#248#0#213#246#249#0#209#246#252#0#216#247 - +#250#0#215#248#251#0#210#249#254#0#214#249#253#0#208#253#255#0#214#254#255#0 - +#217#248#251#0#221#250#251#0#217#249#252#0#221#250#252#0#218#253#255#0#221 - +#253#254#0#231#238#224#0#226#243#236#0#225#254#254#0#229#255#255#0#233#255 - +#255#0#144#0'b'#0#176#0'x'#0#207#0#142#0#240#0#164#0#255#17#179#0#255'1'#190 - +#0#255'Q'#199#0#255'q'#209#0#255#145#220#0#255#177#229#0#255#209#240#0#255 - +#255#255#0#0#0#0#0','#0'/'#0'K'#0'P'#0'i'#0'p'#0#135#0#144#0#165#0#176#0#196 - +#0#207#0#225#0#240#0#240#17#255#0#242'1'#255#0#244'Q'#255#0#246'q'#255#0#247 - +#145#255#0#249#177#255#0#251#209#255#0#255#255#255#0#0#0#0#0#27#0'/'#0'-'#0 - +'P'#0'?'#0'p'#0'R'#0#144#0'c'#0#176#0'v'#0#207#0#136#0#240#0#153#17#255#0#166 - +'1'#255#0#180'Q'#255#0#194'q'#255#0#207#145#255#0#220#177#255#0#235#209#255#0 - +#255#255#255#0#0#0#0#0#8#0'/'#0#14#0'P'#0#21#0'p'#0#27#0#144#0'!'#0#176#0'&' - +#0#207#0','#0#240#0'>'#17#255#0'X1'#255#0'qQ'#255#0#140'q'#255#0#166#145#255 - +#0#191#177#255#0#218#209#255#0#255#255#255#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0''''#24#17#17#19#17#18#24''''#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0')'#18#20#31'$,,,,,$'#31#20#18')'#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#23#21'$389GGGGG973$'#21#23#0#0#0#0#0#0#0#0#0#0#0#0#0#0#17'!39H{' - +#191#195#195#195#195#195#191'{H93!'#17#0#0#0#0#0#0#0#0#0#0#0#0#19'25B}'#194 - +#194#194#194#194#195#194#194#194#194#194'}B52'#19#0#0#0#0#0#0#0#0#0#0#17'2v'#184#182#187#180#177#177#187#189#190#189 - +#179#177#177#177#179#177#177#180#187#182#184'v>+('#0#0#0#24'@6'#153#181#177 - +#177#177#176#177#189'he'#190#182#177#176#176#176#177#177#177#176#177#181#153 - +'6@'#24#0#0#0#16'S0'#183#181#176#178#176#176#176#184'l'#7'd'#190#182#177#176 - ,#176#176#176#176#176#176#178#183'0S'#16#0#0#0#16'U&'#175#172#178#182#178#176 - +#169#178#184'h'#4']'#189#178#169#169#169#176#178#181#181#172#174'0U'#15#0#0#0 - +#15'W&'#174#172'jki'#172#163#169#178#189'g'#1#10#181#169#163#163#171'iki'#178 - +#173'&W'#15#0#0#0#15'X%'#183#170#171#172#171#168#162#168#171#184'c'#2#11#181 - +#162#162#162#168#171#171#171#170#183'%X'#15#0#0#0#15'Y%'#175#161#162#162#162 - +#162#162#170#183'a'#3'a'#183#170#162#162#162#162#162#162#162#161#175'%Y'#15#0 - +#0#0#25'R1'#175#159#158#158#158#162#168#175'c'#5'c'#183#168#162#158#158#158 - +#158#158#158#158#165#175'1R'#25#0#0#0':;E'#151#150#159#160#159#165#173'd'#6 - +'d'#174#165#157#156#156#158#156#156#160#160#160#164#151'E;:'#0#0#0#0#26'Zs' - +#166#160'_'#165#173'g'#8'g'#173#160#157#155#156#155#155#155#155#160'_'#160 - +#166'sZ'#26#0#0#0#0#0#18'RC'#152#149#164#164'i'#9'j'#167#160#144#157#144#143 - +#143#143#143#144#144#147#149#152'CR'#18#0#0#0#0#0#0#30'u?'#148#146#164'ik' - +#167#147#144#149'^'#149#143#143#144#147#144#146#146#148'?u'#30#0#0#0#0#0#0#0 - +#25'@TO'#134#139#150'b'#147#145#142#147'b'#147#139#140#145'['#146#137#134'OV' - +'@'#25#0#0#0#0#0#0#0#0#14'tTK'#129#133#141#145#140#140#146'\'#146#140#140#145 - +#138#133#129'KTt'#14#0#0#0#0#0#0#0#0#0#0#19'uV.r'#131#130#132#135#136#137#136 - +#135#132#130#131'r.Zt'#19#0#0#0#0#0#0#0#0#0#0#0#0#14'MxD*p~'#128#127#127#127 - +#128'~p*DxL'#13#0#0#0#0#0#0#0#0#0#0#0#0#0#0#25' tyP/'#29#29#29#29#29'/Pyt ' - +#25#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#18#28'Nw|||||wN'#28#18#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0'J'#27#12#12#12#12#12#27'J'#0#0#0#0#0#0#0#0#0#0#0 - +#255#255#255#255#255#255#255#255#255#255#255#255#255#240#7#255#255#128#0#255 - +#255#0#0#127#254#0#0'?'#252#0#0#31#248#0#0#15#240#0#0#7#224#0#0#3#224#0#0#3 - +#224#0#0#3#192#0#0#1#192#0#0#1#192#0#0#1#192#0#0#1#192#0#0#1#192#0#0#1#192#0 - +#0#1#192#0#0#1#192#0#0#1#224#0#0#3#224#0#0#3#240#0#0#7#240#0#0#7#248#0#0#15 - +#252#0#0#31#254#0#0'?'#255#0#0#127#255#192#1#255#255#240#7#255'('#0#0#0#16#0 - +#0#0' '#0#0#0#1#0#8#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#180'~='#0#194#127'/'#0#173'~E'#0#167'~J'#0#200#134'9'#0#201#136':'#0#185#132 - +'C'#0#184#139'R'#0#150#129'e'#0#153#133'l'#0#163#134'`'#0#164#146'z'#0#162 - +#147'~'#0#167#150#127#0#180#153'v'#0#182#155'x'#0#185#158'z'#0#200#138'A'#0 - +#204#142'E'#0#209#143'B'#0#199#144'N'#0#206#145'I'#0#207#147'N'#0#209#145'G' - +#0#213#147'G'#0#215#149'G'#0#217#151'I'#0#217#153'O'#0#192#149'^'#0#199#152 - +']'#0#207#155'['#0#210#152'T'#0#221#157'V'#0#212#157'['#0#215#160']'#0#224 - +#160'Y'#0#204#158'd'#0#223#164'c'#0#221#167'e'#0#220#168'g'#0#220#168'h'#0 - +#215#169'u'#0#222#182'~'#0#226#168'i'#0#224#171'n'#0#229#170'l'#0#227#174't' - +#0'|'#138#138#0'}'#140#141#0#130#145#146#0#133#148#149#0#137#149#149#0#135 - +#152#154#0#137#153#154#0#181#167#139#0#181#167#140#0#185#167#137#0#142#166 - +#170#0#151#184#190#0#183#189#178#0#197#168#129#0#203#172#131#0#206#180#142#0 - +#215#192#151#0#214#195#152#0#210#197#157#0#225#197#149#0#205#197#170#0#220 - +#214#183#0#216#214#187#0#227#220#190#0#171#196#199#0#175#197#199#0#163#205 - +#214#0#168#208#215#0#173#211#219#0#174#213#219#0#175#211#222#0#191#208#208#0 - +#186#212#215#0#173#221#239#0#189#219#224#0#188#220#225#0#175#224#238#0#177 - +#226#238#0#180#230#241#0#186#232#242#0#190#234#241#0#191#236#243#0#185#232 - +#246#0#188#235#244#0#190#238#247#0#224#228#208#0#197#224#227#0#197#225#228#0 - +#203#228#230#0#207#230#230#0#201#235#237#0#194#233#242#0#194#236#243#0#195 - +#237#245#0#196#237#245#0#200#239#244#0#201#241#246#0#205#241#246#0#194#241 - +#248#0#199#242#249#0#201#242#248#0#206#243#248#0#203#247#251#0#205#245#250#0 - +#205#248#253#0#210#244#244#0#209#245#248#0#215#246#249#0#214#250#253#0#219 - +#249#251#0#220#250#250#0#217#249#252#0#220#250#252#0#220#252#254#0#228#238 - +#224#0#230#250#247#0#225#252#250#0#225#252#253#0#231#255#255#0#255#255#255#0 - +#0#0#0#0'/&'#0#0'PA'#0#0'p['#0#0#144't'#0#0#176#142#0#0#207#169#0#0#240#195#0 - +#0#255#210#17#0#255#216'1'#0#255#221'Q'#0#255#228'q'#0#255#234#145#0#255#240 - +#177#0#255#246#209#0#255#255#255#0#0#0#0#0'/'#20#0#0'P"'#0#0'p0'#0#0#144'>'#0 - +#0#176'M'#0#0#207'['#0#0#240'i'#0#0#255'y'#17#0#255#138'1'#0#255#157'Q'#0#255 - +#175'q'#0#255#193#145#0#255#210#177#0#255#229#209#0#255#255#255#0#0#0#0#0'/' - +#3#0#0'P'#4#0#0'p'#6#0#0#144#9#0#0#176#10#0#0#207#12#0#0#240#14#0#0#255' '#18 - +#0#255'>1'#0#255'\Q'#0#255'zq'#0#255#151#145#0#255#182#177#0#255#212#209#0 - +#255#255#255#0#0#0#0#0'/'#0#14#0'P'#0#23#0'p'#0'!'#0#144#0'+'#0#176#0'6'#0 - +#207#0'@'#0#240#0'I'#0#255#17'Z'#0#255'1p'#0#255'Q'#134#0#255'q'#156#0#255 - +#145#178#0#255#177#200#0#255#209#223#0#255#255#255#0#0#0#0#0'/'#0' '#0'P'#0 - +'6'#0'p'#0'L'#0#144#0'b'#0#176#0'x'#0#207#0#142#0#240#0#164#0#255#17#179#0 - +#255'1'#190#0#255'Q'#199#0#255'q'#209#0#255#145#220#0#255#177#229#0#255#209 - +#240#0#255#255#255#0#0#0#0#0','#0'/'#0'K'#0'P'#0'i'#0'p'#0#135#0#144#0#165#0 - +#176#0#196#0#207#0#225#0#240#0#240#17#255#0#242'1'#255#0#244'Q'#255#0#246'q' - +#255#0#247#145#255#0#249#177#255#0#251#209#255#0#255#255#255#0#0#0#0#0#27#0 - ,'/'#0'-'#0'P'#0'?'#0'p'#0'R'#0#144#0'c'#0#176#0'v'#0#207#0#136#0#240#0#153#17 - +#255#0#166'1'#255#0#180'Q'#255#0#194'q'#255#0#207#145#255#0#220#177#255#0#235 - +#209#255#0#255#255#255#0#0#0#0#0#8#0'/'#0#14#0'P'#0#21#0'p'#0#27#0#144#0'!'#0 - +#176#0'&'#0#207#0','#0#240#0'>'#17#255#0'X1'#255#0'qQ'#255#0#140'q'#255#0#166 - +#145#255#0#191#177#255#0#218#209#255#0#255#255#255#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#13#9#9#10#0#0#0#0#0#0#0#0#0#0#11#2#20#27#27#26#6#1#0#0#0 - +#0#0#0#0#3#26'Cz~~{G'''#5#0#0#0#0#0#4#28']a}~O}a|+'#6#0#0#0#0#24'Eyxux`vxxv)' - +#7#0#0#8')t^swwwssw_F'#24#0#0#18'ArrrI4trrrrq!'#14#0#19'BSHmr40oiRHp$'#12#0 - +#22'Bllho21lghln$'#12#0#21'@efk32kddffb!'#0#0#15'-cM65jYXXYLD'#22#0#0#0#23'?' - +'Z:\[;WKWP,'#16#0#0#0#0'&>NTVJUQ'#255#205#251#255#255#207#245#252#255#210#247#252#255#211#248 - +#253#255#210#247#252#255#208#244#249#255#206#243#248#255#209#245#250#255#215 - +#255#255#255#132#142#142#255'C=;'#255't'#128#128#255#218#255#255#255#209#246 - +#251#255#206#242#247#255#206#242#247#255#206#242#247#255#208#244#249#255#210 - +#247#252#255#211#248#253#255#210#247#252#255#207#245#252#255#205#251#255#255 - +#217#146'>'#255#232#174'r'#255#176'n'#22#255#0#0#0#22#0#0#0#0#0#0#0#22#176'm' - +#22#255#233#176'v'#255#217#144'='#255#203#250#255#255#207#246#252#255#131#144 - +#145#255#133#147#148#255#131#144#145#255#207#244#250#255#204#241#247#255#205 - +#242#247#255#208#246#252#255#218#255#255#255#129#139#141#255'711'#255'nxz' - +#255#211#250#255#255#205#242#247#255#204#241#246#255#204#241#246#255#207#244 - +#250#255#131#144#145#255#133#147#148#255#131#144#145#255#207#246#252#255#203 - +#250#255#255#217#144'='#255#233#176'v'#255#176'm'#22#255#0#0#0#22#0#0#0#0#0#0 - +#0#22#176'm'#22#255#235#178'y'#255#215#142';'#255#208#253#255#255#202#243#250 - +#255#205#245#250#255#206#246#251#255#205#245#250#255#202#242#247#255#201#240 - +#245#255#201#241#246#255#205#246#251#255#215#255#255#255'y'#135#137#255'<44' - +#255'p{}'#255#207#249#254#255#202#241#246#255#201#240#245#255#201#240#245#255 - +#202#242#247#255#205#245#250#255#206#246#251#255#205#245#250#255#202#243#250 - +#255#208#253#255#255#215#142';'#255#235#178'y'#255#176'm'#22#255#0#0#0#22#0#0 - +#0#0#0#0#0#19#176'm'#22#255#237#183'~'#255#214#141':'#255#207#252#255#255#198 - +#241#247#255#200#240#245#255#200#240#245#255#200#240#245#255#199#239#244#255 - +#200#240#245#255#202#244#249#255#210#253#255#255'x'#132#133#255'A88'#255'w' - +#131#133#255#210#254#255#255#202#243#248#255#199#239#244#255#199#239#244#255 - +#199#239#244#255#199#239#244#255#200#240#245#255#200#240#245#255#200#240#245 - +#255#198#241#247#255#207#252#255#255#214#141':'#255#237#183'~'#255#176'm'#22 - +#255#0#0#0#19#0#0#0#0#0#0#0#14#172'k'#22#230#227#170'l'#255#218#147'D'#255 - +#208#251#255#255#196#239#248#255#197#237#245#255#197#237#245#255#197#237#244 - +#255#198#238#245#255#200#241#249#255#207#251#255#255'{'#134#136#255'H?='#255 - +'z'#134#136#255#208#252#255#255#201#242#249#255#198#238#245#255#197#237#244 - +#255#197#237#244#255#197#237#244#255#197#237#244#255#197#237#244#255#197#237 - +#245#255#197#237#245#255#196#239#248#255#208#251#255#255#218#147'D'#255#227 - ,#170'l'#255#172'k'#22#230#0#0#0#14#0#0#0#0#0#0#0#7#166'i'#23#179#212#153'T' - +#255#225#158'X'#255#210#213#196#255#193#240#251#255#195#238#246#255#196#239 - +#246#255#196#238#246#255#197#241#248#255#203#249#255#255'~'#138#139#255'NFC' - +#255'}'#137#139#255#204#250#255#255#197#241#248#255#194#237#244#255#194#236 - +#243#255#194#236#243#255#194#236#243#255#194#236#243#255#194#236#243#255#195 - +#238#245#255#196#239#246#255#195#238#246#255#193#240#251#255#210#213#196#255 - +#225#158'X'#255#212#153'T'#255#166'i'#23#179#0#0#0#7#0#0#0#0#0#0#0#1#148'^' - +#22'V'#188'{*'#255#240#182'{'#255#207#176#130#255#196#240#255#255#194#240#248 - +#255'u'#131#133#255#197#241#249#255#200#248#255#255#128#141#143#255'ULJ'#255 - +#128#141#142#255#201#249#255#255#195#240#247#255#193#237#244#255#192#235#242 - +#255#192#235#242#255#192#235#242#255#192#235#242#255#192#235#242#255#192#235 - +#243#255#194#238#245#255'u'#131#133#255#194#240#248#255#196#240#255#255#207 - +#176#130#255#240#182'{'#255#188'{*'#255#148'^'#22'V'#0#0#0#1#0#0#0#0#0#0#0#0 - +#0#0#0#9#175'm'#22#247#228#171'm'#255#224#155'Q'#255#196#217#216#255#191#236 - +#249#255#191#239#247#255#194#242#250#255#130#142#142#255'\RO'#255#132#144#146 - +#255#198#247#255#255#192#239#246#255#191#237#244#255#192#238#245#255#191#236 - +#243#255#189#234#241#255#189#234#241#255#189#234#241#255#189#235#241#255#189 - +#235#242#255#190#236#243#255#191#239#246#255#191#236#249#255#196#217#216#255 - +#224#155'Q'#255#228#171'm'#255#175'm'#22#247#0#0#0#9#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#2#157'b'#23'f'#193#129'3'#255#242#186#131#255#209#155'Z'#255#185#228 - +#250#255#187#235#246#255#192#241#250#255#131#143#143#255#134#147#148#255#196 - +#247#255#255#190#237#246#255#188#235#243#255#191#238#247#255'r'#131#134#255 - +#191#238#247#255#188#234#242#255#187#233#241#255#189#235#243#255#189#236#245 - +#255#189#235#243#255#187#235#244#255#187#234#245#255#185#228#250#255#209#155 - +'Z'#255#242#186#130#255#193#129'3'#255#157'b'#23'g'#0#0#0#2#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#6#175'm'#23#231#217#159'['#255#234#173'l'#255#198#166'z' - +#255#177#222#245#255#184#231#244#255#190#242#253#255'q'#133#136#255#189#238 - +#247#255#185#232#242#255#185#232#242#255#189#237#247#255'q'#132#135#255#189 - +#237#247#255#185#232#242#255#184#232#241#255#187#235#244#255'm~'#129#255#187 - +#237#246#255#182#228#241#255#176#222#245#255#198#166'z'#255#237#176'q'#255 - +#217#159'['#255#175'm'#23#231#0#0#0#6#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#1#142'Z'#21''''#175'l'#20#255#238#184#128#255#235#174'm'#255#198#158 - +'k'#255#172#211#230#255#175#221#241#255#183#232#245#255#184#235#244#255#183 - +#232#241#255#183#232#241#255#187#236#245#255'n'#128#130#255#187#236#245#255 - +#183#232#241#255#183#232#241#255#184#235#244#255#182#231#244#255#174#221#240 - +#255#172#211#230#255#197#158'j'#255#235#174'm'#255#238#184#128#255#175'l'#20 - +#255'nE'#17#18#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#1#157'b'#23'8'#180'r'#28#255#239#186#131#255#237#177'r'#255#207#145'L'#255 - +#181#182#167#255#162#210#238#255#166#211#233#255#170#218#234#255#175#224#238 - +#255#178#228#240#255#179#229#242#255#178#228#240#255#175#224#238#255#170#218 - +#234#255#166#211#233#255#162#210#238#255#181#182#167#255#207#145'L'#255#240 - +#181'x'#255#239#186#130#255#180'r'#28#255#143'Z'#21'#'#0#0#0#1#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#147'\'#21'"'#175 - +'l'#20#255#220#163'b'#255#249#198#146#255#227#159'W'#255#202#139'D'#255#178 - +#174#153#255#164#191#200#255#153#205#236#255#154#204#235#255#154#204#235#255 - +#154#204#235#255#153#205#236#255#164#191#200#255#178#174#153#255#202#139'D' - +#255#227#159'W'#255#249#198#146#255#219#163'b'#255#175'l'#19#255#148']'#22'#' - +#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0'pF'#17#16#175'l'#22#230#195#132'7'#255#237#184#128#255 - +#252#201#150#255#230#165'a'#255#218#144'C'#255#208#130'-'#255#209#130','#255 - +#209#130','#255#209#130','#255#208#130'-'#255#218#144'C'#255#230#165'a'#255 - +#252#201#150#255#237#184#128#255#195#132'7'#255#175'l'#22#230'pF'#17#16#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2#167'i'#23'_'#175'm'#21#247#190#127'.' - +#255#222#167'g'#255#242#193#141#255#255#213#169#255#255#213#168#255#255#213 - +#168#255#255#213#168#255#255#213#169#255#242#193#141#255#222#167'g'#255#190 - +#127'.'#255#175'm'#21#247#167'i'#23'_'#0#0#0#2#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#1#167'i'#24'L'#172'k'#22#172#175'l'#21 - +#226#175'k'#19#255#175'k'#19#255#175'k'#19#255#175'k'#19#255#175'k'#19#255 - +#175'l'#21#226#172'k'#22#172#167'i'#24'L'#0#0#0#1#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#255#255#255#255#255#192#1#255 - +#255#0#0#127#254#0#0'?'#248#0#0#15#240#0#0#7#240#0#0#7#224#0#0#3#192#0#0#1 - +#192#0#0#1#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0 - ,#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#128#0#0#0#192#0#0#1 - +#192#0#0#1#224#0#0#3#224#0#0#7#240#0#0#7#248#0#0#15#254#0#0'?'#255#0#0#127 - +#255#192#1#255'('#0#0#0#16#0#0#0' '#0#0#0#1#0' '#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2#0#0#0 - +#8#0#0#0#11#0#0#0#11#0#0#0#10#0#0#0#5#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 - +#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#2#0#0#0#18#25#16#4' 1 then begin + // WriteLn('has options'); + //if Application.HasOption('h', 'help') then begin + // WriteLn('SnapTimer usage'); + // WriteLn('[number of minutes] (must be the only parameter)'); + // WriteLn('-h|--help\tShow this usage'); + // WriteLn('-i|--ini=\tPath to .ini file to use'); + //end else if Application.HasOption('i', 'ini') then begin + // //IniFilename := Application.GetOptionValue('i', 'ini'); + // WriteLn('ini option found'); + //end; + //end; + + if Config.Load = False then + begin + // When will this code execute? + MessageDlg(MSG_OPEN_INI, mtError, [mbOK], 0); + Exit; + end; + + // Use TimeEdit argument if it's the only parameter and it's numeric + if (ParamCount = 1) and (TUtils.IsInteger(ParamStr(1))) then + TimeEdit.Value := StrToInt(ParamStr(1)); + + if Config.AutoStart then + ToggleCountdown(Sender); +end; + + +procedure TMainForm.OnDestroyForm(Sender: TObject); +begin + if GetConfig.AutoSave then + SaveSettings(Sender); +end; + +procedure TMainForm.OnShowForm(Sender: TObject); +begin + if OnShowFormFirstTime then + begin + OnShowFormFirstTime:= False; + ApplyConfig; + // ApplyConfig does not set MainForm dimensions and TimeEdit(TSpinEdit) + TimeEdit.Value:= GetConfig.Minutes; + MyTimer.Reset; // This will trigger OnTimerStateChanged + end; + + // TODO Get this working for window size (getpreferred size gets form, not window) + // Get preferred size before font change, then after and add or subtract that delta from the + // window size? + // AutoSize is working on startup, just not when font size is changed. + // Need to find the method that will set the window to the preferred size, because the preferred size is correct + // setBounds does it, but it also requires + if FontSizeChanged then + begin + //AutoSize:= True; + //MainForm.FontChanged(Sender); + //GetPreferredSize(w, h, True); + //cRect := GetClientRect; + //cRect.Right := w; + //cRect.Bottom := h; + //AdjustClientRect(cRect); + + // These two lines cause an exception - why I don't know + //Count.Width:= w; + //Count.Height:= h; + + //InvalidatePreferredSize; + //Repaint; + //Resize; + //AdjustSize; + + //GetPreferredSize(w, h, True); + //ShowMessageFmt('width: %d, height: %d', [w, h]); + //ShowMessageFmt('width: %d, height: %d', [FormerWidth, FormerHeight]); + //WndWidth:= WndWidth + (w - FormerWidth); + //WndHeight:= WndHeight + (h - FormerHeight); + //SetBounds(Monitor.WorkareaRect.Right - w + rightAdjust, 0, w, h); + //WndWidth:= w; + //WndHeight:= h; + //ShowMessageFmt('width: %d, height: %d', [w, h]); + //exit; + end; +end; + +procedure TMainForm.FormActivate(Sender: TObject); +begin + WindowState := wsNormal; + ShowInTaskBar := stDefault; + Show; +end; + +procedure TMainForm.FormWindowStateChange(Sender: TObject); +begin + if not GetConfig.MinToTray then + Exit; + + if WindowState = wsMinimized then + begin + WindowState := wsNormal; + ShowInTaskBar := stNever; + Hide; + end; +end; + +procedure TMainForm.FormKeyPress(Sender: TObject; var Key: char); +begin + // TMainForm's KeyPreview property must be set to True for this to work. + if key = #27 then + Close; // 27 = Escape + if (TimeEdit.Focused) and (key = #13) then + ToggleCountdown(Sender); +end; + +procedure TMainForm.CloseApp(Sender: TObject); +begin + OnDestroyForm(Sender); + Close; +end; + + +procedure TMainForm.ToggleCountdown(Sender: TObject); +begin + if (Sender.ClassName = Count.ClassName) and (not GetConfig.ClickTime) then + Exit; + + // We want to go from `Finished` state directly to `Running` state. (or not?) + if MyTimer.State = TState.Finished then + MyTimer.Reset; + + MyTimer.Toggle; +end; + +procedure TMainForm.ResetCountdown(Sender: TObject); +begin + if (Sender.ClassName = Count.ClassName) and (not GetConfig.DblClickTime) then + Exit; + + MyTimer.Reset; +end; + +procedure TMainForm.OnTick(Sender: TObject); +begin + UpdateTime(); +end; + +procedure TMainForm.OnTimerStateChanged(Sender: TObject); +var Config: TConfig; +begin + Config:= GetConfig; + + case MyTimer.State of + Ready: + begin + if Config.SecondsMode then + MyTimer.Seconds := TimeEdit.Value + else + MyTimer.Minutes:= TimeEdit.Value; + UpdateTime(); + UpdateButtonsAndMenus; + TrayIconMain.Icon:= ImgIconMain.Picture.Icon; + end; + + Running: + begin + UpdateButtonsAndMenus; + TrayIconMain.Icon:= ImgIconRunning.Picture.Icon; + if Config.TickingOn then + PlayTicking(); + end; + + Paused: + begin + UpdateButtonsAndMenus; + TrayIconMain.Icon:= ImgIconPaused.Picture.Icon; + TUtils.StopAudio; + end; + + Finished: + begin + UpdateTime(); + UpdateButtonsAndMenus; + TUtils.StopAudio; + + Application.Title := APP_NAME; + if Config.DoneAudioEnabled then + TUtils.PlayAudio(Config.DoneAudio, Config.LoopAudio); + if Config.DoneAppEnabled then + TUtils.RunApp(Config.DoneApp); + if Config.DoneTrayMsgEnabled then + ShowTrayMessage(Config.DoneTrayMsg); + if Config.DoneMessageEnabled then + ShowDoneMessage(Config.DoneMessage); + if Config.AutoRestart then + ToggleCountdown(Sender) + else + TrayIconMain.Icon := ImgIconDone.Picture.Icon; + end; + end; +end; + +// When it comes to buttons and menus, there are only two states. +procedure TMainForm.UpdateButtonsAndMenus; +begin + if MyTimer.State = TState.Running then + begin + ImgPause.Visible := True; + ImgStart.Visible := False; + MenuToggle.Caption := BTN_PAUSE; + TrayMenuToggle.Caption := BTN_PAUSE; + end + else + begin + ImgPause.Visible := False; + ImgStart.Visible := True; + MenuToggle.Caption := BTN_START; + TrayMenuToggle.Caption := BTN_START; + end; +end; + +// TODO Animate icon between alarm icon and main so it blinks every second +// Instead of playing icon, animate the clock hands so they go around? +// http://delphi.about.com/od/kbwinshell/l/aa122501a.htm +procedure TMainForm.UpdateTime(); +begin + // Changing caption while it's not visible keeps time from flashing + Count.Visible := False; + Count.Caption := TUtils.SecondsToTime(MyTimer.Seconds, GetConfig.HideSeconds); + Count.Visible := True; + MenuCount.Caption := Count.Caption; + if MyTimer.State = TState.Running then + begin + Application.Title := Count.Caption + ' - ' + APP_NAME; + TrayIconMain.Hint := Count.Caption; + end + else + begin + Application.Title := APP_NAME; + TrayIconMain.Hint := APP_NAME; + end; +end; + + +procedure TMainForm.ShowOptions(Sender: TObject); +var + Config: TConfig; + Ok: Boolean; + r: TRect; +begin + OptionsForm := TOptionsForm.Create(Self); + Ok:= OptionsForm.ShowModal = mrOk; + OptionsForm.Free; + + if Ok then + begin + Config:= GetConfig; + GetWindowRect(MainForm.Handle, r); + Config.WndLeft:= r.Left; + Config.WndTop:= r.Top; + Config.WndWidth:= r.Right - r. Left; + Config.WndHeight:= r.Bottom - r.Top; + ApplyConfig; + + //if not (Count.Font.Size = f.Size) then + //begin + //FontSizeChanged := True; + //GetPreferredSize(FormerWidth, FormerHeight, True); + //ShowMessageFmt('width: %d, height: %d', [FormerWidth, FormerHeight]); + //end; + //Count.Font := f; + // TODO Get font colors and background color to be shown - what's up? + + //if FontSizeChanged then OnShowForm(Sender); + if MyTimer.State = TState.Running then + begin + if Config.TickingOn then + PlayTicking + else + TUtils.StopAudio; + end; + + if Config.AutoSave then + SaveSettings(Sender); + // TODO Resize the window if necessary to accomodate larger text + // Get the text size from the font for '00:00:00'? + + end; +end; + + +procedure TMainForm.ShowAbout(Sender: TObject); +var + AboutForm: TAboutForm; +begin + AboutForm := TAboutForm.Create(Self); + AboutForm.ShowModal; + AboutForm.Release; +end; + + +procedure TMainForm.SaveSettings(Sender: TObject); +var + Config: TConfig; + wRect: TRect; +begin + Config:= GetConfig; + + // These config values are not set in OptionsForm. + Config.Minutes:= TimeEdit.Value; + GetWindowRect(self.Handle, wRect); + Config.WndWidth:= wRect.Right - wRect.Left; + Config.WndHeight:= wRect.Bottom - wRect.Top; + Config.WndLeft:= wRect.Left; + Config.WndTop:= wRect.Top; + + if Config.Save = False then + MessageDlg(MSG_WRITE_INI, mtError, [mbOK], 0); +end; + +procedure TMainForm.TimeEditChanged(Sender: TObject); +begin + if (MyTimer.State <> TState.Running) and (MyTimer.State <> TState.Paused) then + ResetCountdown(Sender); +end; + +procedure TMainForm.ToggleCompact(Sender: TObject); +begin + // todo +// SetFieldsVisible(GetConfig.CompactMode); +// CompactMode := not CompactMode; +end; + + +procedure TMainForm.ShowTrayMessage(Msg: string); +begin + with TrayIconMain do + begin + BalloonHint := Msg; + ShowBalloonHint; + BalloonTimeout := 4000; + end; +end; + +procedure TMainForm.ShowDoneMessage(Msg: string); +begin + // http://msdn.microsoft.com/en-us/library/ms645505(VS.85).aspx + Windows.MessageBox(self.Handle, pChar(msg), 'Done', + MB_SYSTEMMODAL or MB_SETFOREGROUND or MB_TOPMOST or MB_ICONINFORMATION); + TUtils.StopAudio; +end; + +// TODO Figure out how to remove the fields from the form so they don't +// take up space anymore (and count can expand to top) +// Easier to create a new form, hide this one and show the other one +// Set border style to none too. +procedure TMainForm.SetFieldsVisible(showFields: boolean); +begin + if showFields then + begin + MainForm.Menu := MainMenu1; + end + else + begin + MainForm.Menu := nil; + TimeLabel.Visible := showFields; + TimeEdit.Visible := showFields; + ImgStart.Visible := showFields; + ImgReset.Visible := showFields; + ImgPause.Visible := showFields; + end; +end; + + + +procedure TMainForm.PlayTicking(); +begin + TUtils.PlayAudio('.\sounds\ticking\ticking.wav', True); +end; + + +procedure TMainForm.ApplyConfig; +var + Config: TConfig; + WndHandle: HWND; + flags: Integer; +begin + Config:= GetConfig; + if Config.AlwaysOnTop then + FormStyle:= fsSystemStayOnTop + else + FormStyle:= fsNormal; + + if Config.SecondsMode then + TimeLabel.Caption := LBL_SECONDS + else + TimeLabel.Caption := LBL_MINUTES; + + Count.Font.Quality := fqAntialiased; + Count.Font.Name := Config.Font.Name; + Count.Font.CharSet := Config.Font.Charset; + Count.Font.Color := Config.Font.Color; + Count.Font.Size := Config.Font.Size; + Count.Font.Style := Config.Font.Style; + Count.Color := Config.Font.BgColor; + + WndHandle:= self.Handle; + Self.Position := poDesigned; + flags:= SWP_SHOWWINDOW; + case Config.WndPosition of + Remember: SetWindowPos(WndHandle, HWND_TOP, Config.WndLeft, Config.WndTop, Config.WndWidth, Config.WndHeight, flags); + TopLeft: SetWindowPos(WndHandle, HWND_TOP, 0, 0, Config.WndWidth, Config.WndHeight, flags); + TopRight: SetWindowPos(WndHandle, HWND_TOP, Monitor.WorkareaRect.Right - Config.WndWidth, 0, Config.WndWidth, Config.WndHeight, flags); + BottomLeft: SetWindowPos(WndHandle, HWND_TOP, 0, Monitor.WorkareaRect.Bottom - Config.WndHeight, Config.WndWidth, Config.WndHeight, flags); + BottomRight: SetWindowPos(WndHandle, HWND_TOP, Monitor.WorkareaRect.Right - Config.WndWidth, + Monitor.WorkareaRect.Bottom - Config.WndHeight, Config.WndWidth, Config.WndHeight, flags); + Center: + begin + SetWindowPos(WndHandle, HWND_TOP, 0, 0, Config.WndWidth, Config.WndHeight, SWP_SHOWWINDOW or SWP_NOMOVE); + Self.Position := poScreenCenter; + end; + end; +end; + +initialization + {$I mainform1.lrs} +end. + diff --git a/settings.lfm b/ui/options.lfm similarity index 68% rename from settings.lfm rename to ui/options.lfm index 2cf15ee..9caaea3 100644 --- a/settings.lfm +++ b/ui/options.lfm @@ -1,25 +1,25 @@ object OptionsForm: TOptionsForm Left = 465 - Height = 351 + Height = 234 Top = 319 - Width = 825 + Width = 550 BorderIcons = [biSystemMenu] BorderStyle = bsDialog Caption = 'Options' - ClientHeight = 351 - ClientWidth = 825 - DesignTimePPI = 144 + ClientHeight = 234 + ClientWidth = 550 KeyPreview = True - OnCreate = EnableFields + OnCreate = FormCreate + OnDestroy = FormDestroy OnKeyPress = FormKeyPress Position = poScreenCenter ShowInTaskBar = stNever LCLVersion = '2.0.10.0' object BtnOk: TButton - Left = 576 - Height = 38 - Top = 300 - Width = 112 + Left = 384 + Height = 25 + Top = 200 + Width = 75 Caption = '&OK' Default = True ModalResult = 1 @@ -27,10 +27,10 @@ object OptionsForm: TOptionsForm TabOrder = 1 end object BtnCancel: TButton - Left = 696 - Height = 38 - Top = 300 - Width = 112 + Left = 464 + Height = 25 + Top = 200 + Width = 75 Caption = '&Cancel' ModalResult = 2 ParentFont = False @@ -38,24 +38,24 @@ object OptionsForm: TOptionsForm end object PageControl1: TPageControl Left = 0 - Height = 288 + Height = 192 Top = 0 - Width = 828 - ActivePage = GeneralTab + Width = 552 + ActivePage = FontsTab ParentFont = False - TabIndex = 0 + TabIndex = 2 TabOrder = 0 object GeneralTab: TTabSheet Caption = 'General' - ClientHeight = 250 - ClientWidth = 820 + ClientHeight = 164 + ClientWidth = 544 ParentFont = False object CheckAutostart: TCheckBox - Left = 256 - Height = 29 + Left = 171 + Height = 19 Hint = 'Begin counting down when the application starts' - Top = 111 - Width = 150 + Top = 74 + Width = 100 Caption = '&Autostart timer' ParentFont = False ParentShowHint = False @@ -63,11 +63,11 @@ object OptionsForm: TOptionsForm TabOrder = 4 end object CheckMinToTray: TCheckBox - Left = 18 - Height = 29 + Left = 12 + Height = 19 Hint = 'Don''t show the application in the taskbar when minimized' - Top = 159 - Width = 158 + Top = 106 + Width = 106 Caption = '&Minimize to tray' ParentFont = False ParentShowHint = False @@ -75,23 +75,23 @@ object OptionsForm: TOptionsForm TabOrder = 2 end object GroupBox2: TGroupBox - Left = 6 - Height = 72 - Top = 19 - Width = 240 + Left = 4 + Height = 48 + Top = 13 + Width = 160 Caption = 'Starting position' - ClientHeight = 42 - ClientWidth = 236 + ClientHeight = 28 + ClientWidth = 156 ParentFont = False TabOrder = 0 object PositionCombo: TComboBox - Left = 9 - Height = 33 + Left = 6 + Height = 23 Hint = 'Where to place the application window when it starts' - Top = 3 - Width = 216 + Top = 2 + Width = 144 DropDownCount = 6 - ItemHeight = 25 + ItemHeight = 15 ParentFont = False ParentShowHint = False ShowHint = True @@ -100,11 +100,11 @@ object OptionsForm: TOptionsForm end end object CheckClickTime: TCheckBox - Left = 256 - Height = 29 + Left = 171 + Height = 19 Hint = 'Click the timer numerals to start/pause the timer' - Top = 159 - Width = 168 + Top = 106 + Width = 113 Caption = 'Cl&ick time to start' ParentFont = False ParentShowHint = False @@ -112,11 +112,11 @@ object OptionsForm: TOptionsForm TabOrder = 5 end object CheckDblClickTime: TCheckBox - Left = 256 - Height = 29 + Left = 171 + Height = 19 Hint = 'Double-click the timer numerals to reset the timer' - Top = 207 - Width = 233 + Top = 138 + Width = 156 Caption = '&Double-click time to reset' ParentFont = False ParentShowHint = False @@ -124,11 +124,11 @@ object OptionsForm: TOptionsForm TabOrder = 6 end object CheckAutoRestart: TCheckBox - Left = 522 - Height = 29 + Left = 348 + Height = 19 Hint = 'Start the timer again when the time reaches zero' - Top = 111 - Width = 239 + Top = 74 + Width = 161 Caption = 'Automatically &restart timer' ParentFont = False ParentShowHint = False @@ -136,11 +136,11 @@ object OptionsForm: TOptionsForm TabOrder = 8 end object CheckLoopAudio: TCheckBox - Left = 522 - Height = 29 + Left = 348 + Height = 19 Hint = 'Continue playing the audio file until timer is reset' - Top = 159 - Width = 170 + Top = 106 + Width = 113 Caption = '&Loop audio alarm' ParentFont = False ParentShowHint = False @@ -148,11 +148,11 @@ object OptionsForm: TOptionsForm TabOrder = 9 end object CheckAutoSave: TCheckBox - Left = 18 - Height = 29 + Left = 12 + Height = 19 Hint = 'Automatically save settings to the .ini file on exit' - Top = 207 - Width = 171 + Top = 138 + Width = 113 Caption = 'Auto&save settings' ParentFont = False ParentShowHint = False @@ -160,11 +160,11 @@ object OptionsForm: TOptionsForm TabOrder = 3 end object CheckAlwaysOnTop: TCheckBox - Left = 18 - Height = 29 + Left = 12 + Height = 19 Hint = 'Show the window even when it''s deactivated' - Top = 111 - Width = 144 + Top = 74 + Width = 95 Caption = 'Al&ways on top' ParentFont = False ParentShowHint = False @@ -172,11 +172,11 @@ object OptionsForm: TOptionsForm TabOrder = 1 end object CheckTicking: TCheckBox - Left = 522 - Height = 29 + Left = 348 + Height = 19 Hint = 'Play a ticking sound when timer is running' - Top = 204 - Width = 174 + Top = 136 + Width = 117 Caption = '&Play ticking sound' ParentFont = False ParentShowHint = False @@ -184,11 +184,11 @@ object OptionsForm: TOptionsForm TabOrder = 10 end object CheckSecondsMode: TCheckBox - Left = 522 - Height = 29 + Left = 348 + Height = 19 Hint = 'Enter seconds instead of minutes' - Top = 62 - Width = 204 + Top = 41 + Width = 135 Caption = '&Enable seconds mode' ParentFont = False ParentShowHint = False @@ -196,11 +196,11 @@ object OptionsForm: TOptionsForm TabOrder = 7 end object CheckHideSeconds: TCheckBox - Left = 256 - Height = 29 + Left = 171 + Height = 19 Hint = 'Begin counting down when the application starts' - Top = 62 - Width = 137 + Top = 41 + Width = 91 Caption = '&Hide seconds' ParentFont = False ParentShowHint = False @@ -210,148 +210,148 @@ object OptionsForm: TOptionsForm end object AlarmsTab: TTabSheet Caption = 'Alarms' - ClientHeight = 166 + ClientHeight = 164 ClientWidth = 544 ParentFont = False object GroupBox1: TGroupBox - Left = 6 - Height = 216 - Top = 15 - Width = 801 + Left = 4 + Height = 144 + Top = 10 + Width = 534 Caption = 'Notification actions' - ClientHeight = 186 - ClientWidth = 797 + ClientHeight = 124 + ClientWidth = 530 ParentFont = False TabOrder = 0 object NotifyRunAppOn: TCheckBox - Left = 8 - Height = 17 - Top = 138 - Width = 84 + Left = 5 + Height = 19 + Top = 92 + Width = 93 Caption = '&Run program:' OnClick = EnableFields ParentFont = False TabOrder = 10 end object NotifyAudioOn: TCheckBox - Left = 8 - Height = 17 - Top = 94 - Width = 88 + Left = 5 + Height = 19 + Top = 63 + Width = 94 Caption = '&Play wave file:' OnClick = EnableFields ParentFont = False TabOrder = 6 end object NotifyTrayMsgOn: TCheckBox - Left = 8 - Height = 17 - Top = 48 - Width = 104 + Left = 5 + Height = 19 + Top = 32 + Width = 113 Caption = '&Show tray popup:' OnClick = EnableFields ParentFont = False TabOrder = 3 end object NotifyMsgOn: TCheckBox - Left = 8 - Height = 17 - Top = 4 - Width = 101 + Left = 5 + Height = 19 + Top = 3 + Width = 110 Caption = '&Display message:' OnClick = EnableFields ParentFont = False TabOrder = 0 end object NotifyTrayMsgTest: TButton - Left = 678 - Height = 38 - Top = 45 - Width = 112 + Left = 452 + Height = 25 + Top = 30 + Width = 75 Caption = 'Test' OnClick = TestTrayMsg ParentFont = False TabOrder = 5 end object NotifyMsgTest: TButton - Left = 678 - Height = 38 - Top = 2 - Width = 112 + Left = 452 + Height = 25 + Top = 1 + Width = 75 Caption = 'Test' OnClick = TestMessage ParentFont = False TabOrder = 2 end object NotifyRunTest: TButton - Left = 678 - Height = 38 - Top = 135 - Width = 112 + Left = 452 + Height = 25 + Top = 90 + Width = 75 Caption = 'Test' OnClick = TestRunApp ParentFont = False TabOrder = 13 end object NotifyAudioTest: TButton - Left = 678 - Height = 38 - Top = 92 - Width = 112 + Left = 452 + Height = 25 + Top = 61 + Width = 75 Caption = 'Test' OnClick = TestAudio ParentFont = False TabOrder = 9 end object NotifyTrayMsg: TEdit - Left = 195 - Height = 21 - Top = 48 - Width = 472 + Left = 130 + Height = 23 + Top = 32 + Width = 315 ParentFont = False TabOrder = 4 end object NotifyRunBtn: TButton - Left = 558 - Height = 38 - Top = 135 - Width = 112 + Left = 372 + Height = 25 + Top = 90 + Width = 75 Caption = 'Browse' OnClick = GetAppFile ParentFont = False TabOrder = 12 end object NotifyRunApp: TEdit - Left = 195 - Height = 21 - Top = 138 - Width = 348 + Left = 130 + Height = 23 + Top = 92 + Width = 232 ParentFont = False TabOrder = 11 end object NotifyAudioBtn: TButton - Left = 558 - Height = 38 - Top = 92 - Width = 112 + Left = 372 + Height = 25 + Top = 61 + Width = 75 Caption = 'Browse' OnClick = GetAudioFile ParentFont = False TabOrder = 8 end object NotifyAudio: TEdit - Left = 195 - Height = 21 - Top = 94 - Width = 348 + Left = 130 + Height = 23 + Top = 63 + Width = 232 ParentFont = False TabOrder = 7 end object NotifyMsg: TEdit - Left = 195 - Height = 21 - Top = 4 - Width = 472 + Left = 130 + Height = 23 + Top = 3 + Width = 315 ParentFont = False TabOrder = 1 end @@ -359,34 +359,33 @@ object OptionsForm: TOptionsForm end object FontsTab: TTabSheet Caption = 'Font' - ClientHeight = 250 - ClientWidth = 820 - OnShow = UpdateFonts + ClientHeight = 164 + ClientWidth = 544 ParentFont = False object TimerFontBox: TGroupBox - Left = 6 - Height = 156 - Top = 15 - Width = 801 + Left = 4 + Height = 104 + Top = 10 + Width = 534 Caption = 'Timer font' - ClientHeight = 126 - ClientWidth = 797 + ClientHeight = 84 + ClientWidth = 530 ParentFont = False TabOrder = 0 object Label1: TLabel - Left = 9 - Height = 25 - Top = 9 - Width = 35 + Left = 6 + Height = 15 + Top = 6 + Width = 23 Caption = 'Size:' ParentColor = False ParentFont = False end object FontColor: TColorButton - Left = 252 - Height = 38 - Top = 2 - Width = 112 + Left = 168 + Height = 25 + Top = 1 + Width = 75 BorderWidth = 2 ButtonColorSize = 16 ButtonColor = clBlack @@ -394,64 +393,65 @@ object OptionsForm: TOptionsForm ParentFont = False end object BgColor: TColorButton - Left = 543 - Height = 38 - Top = 2 - Width = 112 + Left = 362 + Height = 25 + Top = 1 + Width = 75 BorderWidth = 2 ButtonColorSize = 16 ButtonColor = clSilver - OnColorChanged = UpdateFonts + OnColorChanged = UpdateFontBgColor ParentFont = False end object LabelTextColor: TLabel - Left = 189 - Height = 25 - Top = 9 - Width = 47 + Left = 126 + Height = 15 + Top = 6 + Width = 32 Caption = 'Color:' ParentColor = False ParentFont = False end object LabelTextColor1: TLabel - Left = 429 - Height = 25 - Top = 9 - Width = 99 + Left = 286 + Height = 15 + Top = 6 + Width = 67 Caption = 'Background:' ParentColor = False ParentFont = False end object FontSize: TSpinEdit - Left = 69 - Height = 33 - Top = 4 - Width = 70 + Left = 46 + Height = 23 + Top = 3 + Width = 47 MaxValue = 1000 OnChange = UpdateFontSize ParentFont = False TabOrder = 0 end - object FontName: TStaticText - Left = 141 - Height = 41 - Top = 70 - Width = 96 + object FontPreview: TStaticText + Left = 94 + Height = 28 + Top = 47 + Width = 66 Alignment = taCenter AutoSize = True - BorderSpacing.InnerBorder = 4 + BorderSpacing.InnerBorder = 3 Caption = '00:00:00' - Color = clCaptionText - Font.Height = -24 + Color = clDefault + Font.Height = -16 ParentFont = False ParentColor = False TabOrder = 2 + Transparent = False end object FontNameBtn: TButton - Left = 8 - Height = 38 - Top = 72 - Width = 112 + Left = 5 + Height = 25 + Top = 48 + Width = 75 Caption = '&Font...' OnClick = ChooseFont ParentFont = False @@ -459,13 +459,13 @@ object OptionsForm: TOptionsForm end end object BtnDefaults: TButton - Left = 15 - Height = 38 + Left = 10 + Height = 25 Hint = 'Reset font to default settings' - Top = 201 - Width = 112 + Top = 134 + Width = 75 Caption = '&Defaults' - OnClick = SetDefaults + OnClick = SetFontDefaults ParentFont = False ParentShowHint = False ShowHint = True diff --git a/ui/options.pas b/ui/options.pas new file mode 100644 index 0000000..92258d7 --- /dev/null +++ b/ui/options.pas @@ -0,0 +1,340 @@ +unit Options; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, LResources, Forms, Graphics, Dialogs, StdCtrls, ComCtrls, Spin, Classes; + +type + + { TOptionsForm } + + TOptionsForm = class(TForm) + BtnOk: TButton; + BtnCancel: TButton; + BtnDefaults: TButton; + CheckAutoRestart: TCheckBox; + CheckHideSeconds: TCheckBox; + CheckSecondsMode: TCheckBox; + CheckAutoSave: TCheckBox; + CheckAlwaysOnTop: TCheckBox; + CheckTicking: TCheckBox; + CheckLoopAudio: TCheckBox; + CheckClickTime: TCheckBox; + CheckDblClickTime: TCheckBox; + FontColor: TColorButton; + FontNameBtn: TButton; + CheckAutostart: TCheckBox; + CheckMinToTray: TCheckBox; + BgColor: TColorButton; + FontSize: TSpinEdit; + Label1: TLabel; + FontPreview: TStaticText; + LabelTextColor: TLabel; + LabelTextColor1: TLabel; + TimerFontBox: TGroupBox; + PositionCombo: TComboBox; + GroupBox1: TGroupBox; + GroupBox2: TGroupBox; + NotifyAudio: TEdit; + NotifyAudioBtn: TButton; + NotifyAudioOn: TCheckBox; + NotifyAudioTest: TButton; + NotifyMsg: TEdit; + NotifyMsgOn: TCheckBox; + NotifyMsgTest: TButton; + NotifyRunApp: TEdit; + NotifyRunAppOn: TCheckBox; + NotifyRunBtn: TButton; + NotifyRunTest: TButton; + NotifyTrayMsg: TEdit; + NotifyTrayMsgOn: TCheckBox; + NotifyTrayMsgTest: TButton; + PageControl1: TPageControl; + GeneralTab: TTabSheet; + AlarmsTab: TTabSheet; + FontsTab: TTabSheet; + procedure ChooseFont(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure EnableFields(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure FormKeyPress(Sender: TObject; var Key: char); + procedure GetAudioFile(Sender: TObject); + procedure GetAppFile(Sender: TObject); + function GetFile(Orig: String; FilterStr: String; Dir : String): String; + procedure SetFontDefaults(Sender: TObject); + procedure TestAudio(Sender: TObject); + procedure TestMessage(Sender: TObject); + procedure TestRunApp(Sender: TObject); + procedure TestTrayMsg(Sender: TObject); + procedure UpdateFontColor(Sender: TObject); + procedure UpdateFontBgColor(Sender: TObject); + procedure UpdateFontSize(Sender: TObject); + private + public + { public declarations } + end; + +var + OptionsForm: TOptionsForm; + +implementation + +uses Controls, MainForm1, Config, Utils; +{ TOptionsForm } + +const + FONT_PREVIEW_FONT_SIZE = 18; + +// TODO Move all text strings to the top + +procedure TOptionsForm.GetAudioFile(Sender: TObject); +begin + NotifyAudio.Text := GetFile(NotifyAudio.Text, 'Wave files (*.wav)|*.wav', GetCurrentDir + '\sounds'); +end; + +procedure TOptionsForm.FormKeyPress(Sender: TObject; var Key: char); +begin + if key = #27 then Close; +end; + +procedure TOptionsForm.ChooseFont(Sender: TObject); +var + dlg : TFontDialog; +begin + dlg := TFontDialog.Create(nil); + dlg.Font:= FontPreview.Font; + dlg.Font.Size:= FontSize.Value; + if dlg.Execute then + begin + FontSize.Value:= Dlg.Font.Size; + FontColor.ButtonColor:= Dlg.Font.Color; + FontPreview.Caption:= Dlg.Font.Name + ' : 1234567890'; + FontPreview.Font:= Dlg.Font; + FontPreview.Font.Size:= FONT_PREVIEW_FONT_SIZE; + end; + dlg.Free; +end; + +procedure TOptionsForm.FormCreate(Sender: TObject); +var Config: TConfig; + WndPositions: TStrings; +begin + Config:= GetConfig; + + PageControl1.TabIndex := 0; + + // Main + WndPositions := TStringList.Create; + WndPositions.Add('Centered'); + WndPositions.Add('Remember position'); + WndPositions.Add('Top left'); + WndPositions.Add('Top right'); + WndPositions.Add('Bottom left'); + WndPositions.Add('Bottom right'); + PositionCombo.Items := WndPositions; + PositionCombo.ItemIndex := Ord(Config.WndPosition); + + CheckAlwaysOnTop.Checked:= Config.AlwaysOnTop; + CheckMinToTray.Checked:= Config.MinToTray; + CheckAutoSave.Checked:= Config.AutoSave; + + CheckHideSeconds.Checked:= Config.HideSeconds; + CheckAutostart.Checked:= Config.AutoStart; + CheckClicktime.Checked:= Config.ClickTime; + CheckDblClickTime.Checked:= Config.DblClickTime; + + CheckSecondsMode.Checked:= Config.SecondsMode; + CheckAutoRestart.Checked:= Config.AutoRestart; + CheckLoopAudio.Checked:= Config.LoopAudio; + CheckTicking.Checked:= Config.TickingOn; + + // Alarms + NotifyMsg.Text:= Config.DoneMessage; + NotifyMsgOn.Checked:= Config.DoneMessageEnabled; + + NotifyTrayMsg.Text:= Config.DoneTrayMsg; + NotifyTrayMsgOn.Checked:= Config.DoneTrayMsgEnabled; + + NotifyAudio.Text:= Config.DoneAudio; + NotifyAudioOn.Checked:= Config.DoneAudioEnabled; + + NotifyRunApp.Text:= Config.DoneApp; + NotifyRunAppOn.Checked:= Config.DoneAppEnabled; + + // Fonts + FontSize.Value:= Config.Font.Size; + FontColor.ButtonColor:= Config.Font.Color; + BgColor.ButtonColor:= Config.Font.BgColor; + FontPreview.Caption:= Config.Font.Name + ' : 1234567890'; + FontPreview.Font.Name:= Config.Font.Name; + //FontPreview.Font.Size:= Config.Font.Size; // size is fixed + FontPreview.Font.Size:= FONT_PREVIEW_FONT_SIZE; + FontPreview.Font.CharSet:= Config.Font.Charset; + FontPreview.Font.Color:= Config.Font.Color; + FontPreview.Font.Style:= Config.Font.Style; + FontPreview.Color:= Config.Font.BgColor; + + EnableFields(Sender); +end; + +procedure TOptionsForm.FormDestroy(Sender: TObject); +var Config : TConfig; +begin + if ModalResult = mrOk then + begin + // Update Config + Config:= GetConfig; + // Main + Config.AlwaysOnTop:= CheckAlwaysOnTop.Checked; + Config.MinToTray:= CheckMinToTray.Checked; + Config.AutoStart:= CheckAutostart.Checked; + Config.HideSeconds:= CheckHideSeconds.Checked; + Config.ClickTime:= CheckClicktime.Checked; + Config.DblClickTime:= CheckDblClickTime.Checked; + Config.AutoRestart:= CheckAutoRestart.Checked; + Config.LoopAudio:= CheckLoopAudio.Checked; + Config.TickingOn:= CheckTicking.Checked; + Config.AutoSave:= CheckAutoSave.Checked; + Config.SecondsMode:= CheckSecondsMode.Checked; + + // Placement + Config.WndPosition:= TPosition(PositionCombo.ItemIndex); + + // Alarms + Config.DoneMessage:= NotifyMsg.Text; + Config.DoneMessageEnabled:= NotifyMsgOn.Checked; + Config.DoneTrayMsg:= NotifyTrayMsg.Text; + Config.DoneTrayMsgEnabled:= NotifyTrayMsgOn.Checked; + Config.DoneAudio:= NotifyAudio.Text; + Config.DoneAudioEnabled:= NotifyAudioOn.Checked; + Config.DoneApp:= NotifyRunApp.Text; + Config.DoneAppEnabled:= NotifyRunAppOn.Checked; + + // Fonts + Config.Font.Name:= FontPreview.Font.Name; + Config.Font.Charset:= FontPreview.Font.CharSet; + Config.Font.Color:= FontPreview.Font.Color; + Config.Font.BgColor:= FontPreview.Color; + Config.Font.Size:= FontSize.Value; // FontPreview has fixed size + Config.Font.Style:= FontPreview.Font.Style; + end; +end; + +procedure TOptionsForm.EnableFields(Sender: TObject); +begin + NotifyMsg.Enabled := NotifyMsgOn.Checked; + NotifyMsgTest.Enabled := NotifyMsgOn.Checked; + NotifyTrayMsg.Enabled := NotifyTrayMsgOn.Checked; + NotifyTrayMsgTest.Enabled := NotifyTrayMsgOn.Checked; + + NotifyAudio.Enabled := NotifyAudioOn.Checked; + NotifyAudioBtn.Enabled := NotifyAudioOn.Checked; + NotifyAudioTest.Enabled := NotifyAudioOn.Checked; + + NotifyRunApp.Enabled := NotifyRunAppOn.Checked; + NotifyRunBtn.Enabled := NotifyRunAppOn.Checked; + NotifyRunTest.Enabled := NotifyRunAppOn.Checked; +end; + + +procedure TOptionsForm.UpdateFontSize(Sender: TObject); +begin + //FontPreview.Font.Size:= FontSize.Value; +end; + +procedure TOptionsForm.UpdateFontColor(Sender: TObject); +begin + FontPreview.Font.Color:= FontColor.ButtonColor; +end; + +procedure TOptionsForm.UpdateFontBgColor(Sender: TObject); +begin + FontPreview.Color:= BgColor.ButtonColor; + +end; + +procedure TOptionsForm.GetAppFile(Sender: TObject); +begin + NotifyRunApp.Text := GetFile(NotifyRunApp.Text, 'Executables (*.exe)|*.exe|All files (*.*)|*.*', GetCurrentDir); +end; + +function TOptionsForm.GetFile(Orig: String; FilterStr: String; Dir : String): String; +var + dlg : TOpenDialog; + fname : String; + cwd : String; + relPath : String; +begin + dlg := TOpenDialog.Create(nil); + dlg.FileName := ''; + if DirectoryExists(Dir) then + dlg.InitialDir := Dir + else + dlg.InitialDir := GetCurrentDir; + dlg.Options := [ofFileMustExist]; + dlg.Filter := FilterStr; + dlg.FilterIndex := 1; + + if dlg.Execute then + begin + fname := dlg.FileName; + cwd := GetCurrentDir + '\'; + relPath := ExtractRelativePath(cwd, ExtractFilePath(fname)); + Result := '.\' + relPath + ExtractFileName(fname); + end else begin + Result := Orig; + end; + dlg.Free; +end; + + + +procedure TOptionsForm.SetFontDefaults(Sender: TObject); +var + DefFont : TFontConfig; +begin + DefFont:= GetConfig.GetDefaultFont; + + FontSize.Value:= DefFont.Size; + FontColor.ButtonColor:= DefFont.Color; + BgColor.ButtonColor:= DefFont.BgColor; + FontPreview.Caption:= DefFont.Name + ' : 1234567890'; + FontPreview.Font.Name:= DefFont.Name; + //FontPreview.Font.Size:= DefFont.Size; + FontPreview.Font.CharSet:= DefFont.Charset; + FontPreview.Font.Color:= DefFont.Color; + FontPreview.Font.Style:= DefFont.Style; + FontPreview.Color:= DefFont.BgColor; +end; + +procedure TOptionsForm.TestAudio(Sender: TObject); +begin + with Self.Owner as TMainForm do + TUtils.PlayAudio(NotifyAudio.Text, False); +end; + +procedure TOptionsForm.TestMessage(Sender: TObject); +begin +with Self.Owner as TMainForm do + ShowDoneMessage(NotifyMsg.Text); +end; + +procedure TOptionsForm.TestRunApp(Sender: TObject); +begin + with Self.Owner as TMainForm do + TUtils.RunApp(NotifyRunApp.Text); +end; + +procedure TOptionsForm.TestTrayMsg(Sender: TObject); +begin + with Self.Owner as TMainForm do + ShowTrayMessage(NotifyTrayMsg.Text); +end; + +initialization + {$I options.lrs} +end. + diff --git a/version.inc b/version.inc index 89274e3..e0e84b5 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -'0.2' \ No newline at end of file +'0.3' \ No newline at end of file