├── .gitignore
├── LICENSE
├── README.md
├── changelog.md
├── lib
├── Class_CtlColors.ahk
├── Class_ImageButton.ahk
├── Class_LB_AdjustHeight.ahk
└── Gui_Functions.ahk
├── main.ahk
├── resources
├── menuclip_demo_dark_theme.jpg
└── menuclip_demo_final.gif
└── src
├── MenuClip.ahk
├── config
├── Config.ahk
├── ConfigGuiGeneralOptions.ahk
├── ConfigGuiThemeOptions.ahk
├── ConfigManager.ahk
├── ConfigManagerGui.ahk
└── ConfigThemeManager.ahk
├── controller
├── ClipManager.ahk
├── Controller.ahk
└── TrayManager.ahk
├── model
├── CacheDirManager.ahk
├── ClipStore.ahk
└── Model.ahk
├── test_scenarios.md
└── view
├── MenuClipsViewHandler.ahk
├── MenuGui.ahk
├── MenuSearchHandler.ahk
├── MenuWindowHandler.ahk
└── View.ahk
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | *.ini
3 | nppBackup/
4 | AHK-Studio Backup/
5 | cache/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Except where otherwise stated, this code is licensed under:
2 |
3 | MIT License
4 |
5 | Copyright (c) 2019 takanuva15 (github.com/takanuva15)
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MenuClip
2 | MenuClip is a menu-based clipboard manager written by takanuva15 in AutoHotkey. (Tested on Windows 10 with AHK v1.1.32.00)
3 |
4 | When you press a hotkey, a small context menu will show up that indicates the last x things that were copied to your clipboard.
5 |
6 | #### _This project is in maintenance mode. No new features will be added; bugs may be fixed depending on bug severity and as time permits. (Please check the issue tracker to see if your bug was already raised)_
7 |
8 | ### Demo:
9 |
10 |
11 | Dark theme:
12 |
13 |
14 |
15 | ### Features:
16 | - Stores text clips into memory (doesn't support images or files)
17 | - Displays stored clips in a context menu via `Ctrl+Shift+V`
18 | - Stores 100 clips by default. (Afterwards, it will delete the oldest one and insert the newest clip at the top)
19 | - Has configurable options (eg shortcut, number of clips to store, gui size)
20 | - Can specify editors that use Shift+Insert for pasting instead of Ctrl+V
21 | - Has a dark theme option (can be automatically swapped in at a specified time)
22 | - Saves clips to file so that they are restored when the script is restarted
23 | - Has a search box to filter clips list
24 |
25 | ## How to Run
26 |
27 | 1. Download a zip file of the latest [release](https://github.com/takanuva15/MenuClip/releases).
28 | 1. Extract it to a directory named "MenuClip" wherever you store your scripts.
29 | 1. Double click on `main.ahk` to run it.
30 |
31 | ## Configuring the Script
32 | The configuration can be edited by right-clicking on MenuClip's system tray icon and selecting the `Edit Configuration` option. (Alternatively, you can directly edit the `config.ini` file, which is generated when you run the script for the first time.)
33 | Note:
34 | - When changing the shortcut to open the menu, you must use the AHK shortcut syntax (see [here](https://www.autohotkey.com/docs/Hotkeys.htm#Symbols) for a glossary). Example: To use `CapsLock` + `f` as the hotkey, you should write `CapsLock & f` for the hotkey config option.
35 | - To add a certain editor to the "Shift+Insert pasting" list, add its exe filename to the Shift Ins paste list. (You must comma-separate each exe. eg: `mintty.exe,runemacs.exe` Do not use newlines.)
36 |
37 | ## Contributing
38 | Please open up an issue if you see a bug. Depending on the situation, you can also make PRs, but check with me before starting work on a PR, especially if it is out-of-scope of this application. (eg a rotating clip-paste function which [ClipJump](https://github.com/aviaryan/Clipjump) already does, or something that assigns presets like [this](https://www.autohotkey.com/boards/viewtopic.php?t=65004) already does).
39 |
40 | In addition, if you are not restricted from downloading & running exe files on your computer, you should just download [Ditto](https://ditto-cp.sourceforge.io/) which does the same thing with orders of magnitude more functionality. You can also download [ClipClip](https://clipclip.com/) for Windows. (If you're on a Mac, you can download [ClipMenu](http://www.clipmenu.com/) which has the same essential features)
41 |
42 | Random: I tried my best to structure the code OO-style.
43 |
44 | ## Notes
45 | A [similar application](https://autohotkey.com/board/topic/69834-probably-yet-another-clipboard-manager/) was posted on the AutoHotkey forum by [spg SCOTT](https://www.autohotkey.com/boards/memberlist.php?mode=viewprofile&u=66846). It also fulfills the purpose of a context-based clipboard manager.
46 |
47 | You can check out the Trello board for tracking stories on the project [here](https://trello.com/b/wD95pQRR/menuclip-kanban-board). If you have an issue or bug, feel free to post it on GitHub.
48 |
49 | You can view a UML diagram of the application [here](https://www.lucidchart.com/documents/view/8b32b807-f1e5-4cb6-afa5-1380075d861b). (Note: UML will occasionally be out-of-date)
50 |
51 | ## Credits
52 | This program was developed in [AHK Studio](https://www.autohotkey.com/boards/viewtopic.php?t=300).
53 |
54 | Special thanks to the people who've helped me with coding obstacles by answering my questions on the AutoHotkey forums. You can see my various questions and people's responses [here](https://www.autohotkey.com/boards/search.php?author_id=117081&sr=posts).
55 |
56 | I used the following libraries for this script:
57 | + [`Class_CtlColors.ahk`](https://www.autohotkey.com/boards/viewtopic.php?t=2197)
58 | + [`Class_ImageButton.ahk`](https://www.autohotkey.com/boards/viewtopic.php?t=1103)
59 | + Misc listbox functions combined together in `Class_LB_Functions.ahk`. Please see the comments within the file for relevant links.
60 | + Misc gui functions combined together in `Gui_Functions.ahk`. Please see the comments within the file for relevant links.
61 |
62 | Please note that library files may not be covered under the MIT license. If such is the case, a comment will be placed at the top of the library file indicating its status.
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### v1.15.2
4 | 2020-05-02 Fixed bug where new clips would not be pasted properly due to not being updated in the filtered list array.
5 | 2020-05-02 Fixed bug where fade animation would play when pasting by pressing Enter.
6 | 2020-05-02 Added MenuClip functionality demo gif to repo.
7 | ### v1.15.1
8 | 2020-04-04 Refactored config files into new config directory. Extracted theme calculation logic to its own class.
9 | 2020-04-04 Added logic to swap theme without reloading if user currently has clips view open. (It reloads after you quit)
10 | ## v1.15.0
11 | 2020-03-31 Added a method to clean up the cache each time the script is loaded (the cache randomly gets messed up during normal operation)
12 | ### v1.14.2
13 | 2020-03-19 Fixed issue with converting special characters in display view. Fixed dead code in clip filtering
14 | 2020-03-18 Made the save button in editconfig dark theme
15 | ### v1.14.1
16 | 2020-03-16 Removed fade animation if pasting
17 | ## v1.14.0
18 | 2020-03-16 Added option to swap the theme automatically at a specified time
19 | ### v1.13.1
20 | 2020-03-08 Added tabs to separate theme options from general options
21 | ## v1.13.0
22 | 2020-03-07 Added a fade-away animation to the gui window
23 | ### v1.12.1
24 | 2020-03-07 Changed the left-click tracking to try to mitigate bug where left-click doesn't select correct item in listbox
25 | ## v1.12.0
26 | 2020-02-29 Added option to convert tabs and newlines to [\t] and [\n] when viewing clips
27 | ### v1.11.2
28 | 2020-02-29 Fixed bug that caused search term to remain in box if you exited gui window using Esc
29 | ### v1.11.1
30 | 2020-02-23 Multiple refactorings of the code base to reduce repetition and break down large classes
31 | ## v1.11.0
32 | 2020-02-17 Added tray option to clear cache and reload script
33 | ### v1.10.1
34 | 2020-02-17 Fixed bug that caused click-drag not to work if you clicked outside of gui to close it
35 | ## v1.10.0
36 | 2020-02-16 Added search box to filter clips list
37 | ### v1.9.1
38 | 2020-02-02 Added option to config height. Fixed bug where menu would render offscreen
39 | ## v1.9.0
40 | 2020-02-02 Changed the menu display to be a gui window now. Has some bugs but not going to worry about it since I'm switching to Ditto anyways.
41 | ### v1.8.1
42 | 2020-02-01 Recolored dropdown list in config gui
43 | ## v1.8.0
44 | 2020-01-31 Added a gui window that opens when you edit config (rather than opening up the ini file directly)
45 | ### v1.7.3
46 | 2020-01-19 Converted code structure into MVC
47 | ### v1.7.2
48 | 2020-01-19 Added 1/4 sec delay before adding a clip to the menu, so if an application is rapidly changing the clipboard, only the latest clip will be saved. (Fixes issue when copying text from IntelliJ terminal)
49 | ### v1.7.1
50 | 2020-01-18 Config file is now generated if it doesn't exist
51 | ## v1.7.0
52 | 2020-01-18 Clip menu contents are now saved to a cache folder and will be restored on restart
53 | ### v1.6.2
54 | 2020-01-11 Separated the clips array into its own class
55 | ### v1.6.1
56 | 2020-01-06 Fixed bug that would show identical copies of the same string if it was copied repeatedly consecutively
57 | ## v1.6.0
58 | 2020-01-05 Added option to directly modify config from Tray Menu. (Also modified Tray Menu to custom settings)
59 | ## v1.5.0
60 | 2020-01-05 Added shortcut config option
61 | ## v1.4.0
62 | 2020-01-05 Added a dark theme config option
63 | ## v1.3.0
64 | 2020-01-04 Added support for ini files. Now all configurable variables are read from ini.
65 | ## v1.2.0
66 | 2020-01-04 Added config option to specify which exe processes use Shift+Insert for pasting, and the menu will execute that for those processes
67 | ## v1.1.0
68 | 2020-01-03 Added easily configurable variables to `main.ahk` to modify menu settings
69 | # v1.0.0
70 | 2020-01-01 Refactored menu actions to its own class. Also put in limits to how much of long clips is shown in the menu. Renamed tray tooltip.
71 | 2019-12-29
72 | - Maximum storage size implemented. Currently set to 8 clips.
73 | - Selected menu item now shifts to the top of the menu
74 | 2019-12-22 Clipboard manager now stores clips and displays them in a context menu when you press Ctrl+Shift+V or CapsLock+J. Credit to teadrinker in [this post](https://www.autohotkey.com/boards/viewtopic.php?p=306818&sid=fcabbee4a2f810fb8fe9544a7f8fa688#p306818) for developing that code!
75 | 2019-12-14 Created a UML diagram
76 | 2019-12-08 Repo created. Added a readme and license
77 |
--------------------------------------------------------------------------------
/lib/Class_CtlColors.ahk:
--------------------------------------------------------------------------------
1 | ; ======================================================================================================================
2 | ; AHK 1.1+
3 | ; ======================================================================================================================
4 | ; Function: Auxiliary object to color controls on WM_CTLCOLOR... notifications.
5 | ; Supported controls are: Checkbox, ComboBox, DropDownList, Edit, ListBox, Radio, Text.
6 | ; Checkboxes and Radios accept only background colors due to design.
7 | ; Namespace: CtlColors
8 | ; Tested with: 1.1.25.02
9 | ; Tested on: Win 10 (x64)
10 | ; Change log: 1.0.04.00/2017-10-30/just me - added transparent background (BkColor = "Trans").
11 | ; 1.0.03.00/2015-07-06/just me - fixed Change() to run properly for ComboBoxes.
12 | ; 1.0.02.00/2014-06-07/just me - fixed __New() to run properly with compiled scripts.
13 | ; 1.0.01.00/2014-02-15/just me - changed class initialization.
14 | ; 1.0.00.00/2014-02-14/just me - initial release.
15 | ; ======================================================================================================================
16 | ; This software is provided 'as-is', without any express or implied warranty.
17 | ; In no event will the authors be held liable for any damages arising from the use of this software.
18 | ; ======================================================================================================================
19 | Class CtlColors {
20 | ; ===================================================================================================================
21 | ; Class variables
22 | ; ===================================================================================================================
23 | ; Registered Controls
24 | Static Attached := {}
25 | ; OnMessage Handlers
26 | Static HandledMessages := {Edit: 0, ListBox: 0, Static: 0}
27 | ; Message Handler Function
28 | Static MessageHandler := "CtlColors_OnMessage"
29 | ; Windows Messages
30 | Static WM_CTLCOLOR := {Edit: 0x0133, ListBox: 0x134, Static: 0x0138}
31 | ; HTML Colors (BGR)
32 | Static HTML := {AQUA: 0xFFFF00, BLACK: 0x000000, BLUE: 0xFF0000, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
33 | , LIME: 0x00FF00, MAROON: 0x000080, NAVY: 0x800000, OLIVE: 0x008080, PURPLE: 0x800080, RED: 0x0000FF
34 | , SILVER: 0xC0C0C0, TEAL: 0x808000, WHITE: 0xFFFFFF, YELLOW: 0x00FFFF}
35 | ; Transparent Brush
36 | Static NullBrush := DllCall("GetStockObject", "Int", 5, "UPtr")
37 | ; System Colors
38 | Static SYSCOLORS := {Edit: "", ListBox: "", Static: ""}
39 | ; Error message in case of errors
40 | Static ErrorMsg := ""
41 | ; Class initialization
42 | Static InitClass := CtlColors.ClassInit()
43 | ; ===================================================================================================================
44 | ; Constructor / Destructor
45 | ; ===================================================================================================================
46 | __New() { ; You must not instantiate this class!
47 | If (This.InitClass == "!DONE!") { ; external call after class initialization
48 | This["!Access_Denied!"] := True
49 | Return False
50 | }
51 | }
52 | ; ----------------------------------------------------------------------------------------------------------------
53 | __Delete() {
54 | If This["!Access_Denied!"]
55 | Return
56 | This.Free() ; free GDI resources
57 | }
58 | ; ===================================================================================================================
59 | ; ClassInit Internal creation of a new instance to ensure that __Delete() will be called.
60 | ; ===================================================================================================================
61 | ClassInit() {
62 | CtlColorsInst := New CtlColors
63 | Return "!DONE!"
64 | }
65 | ; ===================================================================================================================
66 | ; CheckBkColor Internal check for parameter BkColor.
67 | ; ===================================================================================================================
68 | CheckBkColor(ByRef BkColor, Class) {
69 | This.ErrorMsg := ""
70 | If (BkColor != "") && !This.HTML.HasKey(BkColor) && !RegExMatch(BkColor, "^[[:xdigit:]]{6}$") {
71 | This.ErrorMsg := "Invalid parameter BkColor: " . BkColor
72 | Return False
73 | }
74 | BkColor := BkColor = "" ? This.SYSCOLORS[Class]
75 | : This.HTML.HasKey(BkColor) ? This.HTML[BkColor]
76 | : "0x" . SubStr(BkColor, 5, 2) . SubStr(BkColor, 3, 2) . SubStr(BkColor, 1, 2)
77 | Return True
78 | }
79 | ; ===================================================================================================================
80 | ; CheckTxColor Internal check for parameter TxColor.
81 | ; ===================================================================================================================
82 | CheckTxColor(ByRef TxColor) {
83 | This.ErrorMsg := ""
84 | If (TxColor != "") && !This.HTML.HasKey(TxColor) && !RegExMatch(TxColor, "i)^[[:xdigit:]]{6}$") {
85 | This.ErrorMsg := "Invalid parameter TextColor: " . TxColor
86 | Return False
87 | }
88 | TxColor := TxColor = "" ? ""
89 | : This.HTML.HasKey(TxColor) ? This.HTML[TxColor]
90 | : "0x" . SubStr(TxColor, 5, 2) . SubStr(TxColor, 3, 2) . SubStr(TxColor, 1, 2)
91 | Return True
92 | }
93 | ; ===================================================================================================================
94 | ; Attach Registers a control for coloring.
95 | ; Parameters: HWND - HWND of the GUI control
96 | ; BkColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
97 | ; ----------- Optional
98 | ; TxColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
99 | ; Return values: On success - True
100 | ; On failure - False, CtlColors.ErrorMsg contains additional informations
101 | ; ===================================================================================================================
102 | Attach(HWND, BkColor, TxColor := "") {
103 | ; Names of supported classes
104 | Static ClassNames := {Button: "", ComboBox: "", Edit: "", ListBox: "", Static: ""}
105 | ; Button styles
106 | Static BS_CHECKBOX := 0x2, BS_RADIOBUTTON := 0x8
107 | ; Editstyles
108 | Static ES_READONLY := 0x800
109 | ; Default class background colors
110 | Static COLOR_3DFACE := 15, COLOR_WINDOW := 5
111 | ; Initialize default background colors on first call -------------------------------------------------------------
112 | If (This.SYSCOLORS.Edit = "") {
113 | This.SYSCOLORS.Static := DllCall("User32.dll\GetSysColor", "Int", COLOR_3DFACE, "UInt")
114 | This.SYSCOLORS.Edit := DllCall("User32.dll\GetSysColor", "Int", COLOR_WINDOW, "UInt")
115 | This.SYSCOLORS.ListBox := This.SYSCOLORS.Edit
116 | }
117 | This.ErrorMsg := ""
118 | ; Check colors ---------------------------------------------------------------------------------------------------
119 | If (BkColor = "") && (TxColor = "") {
120 | This.ErrorMsg := "Both parameters BkColor and TxColor are empty!"
121 | Return False
122 | }
123 | ; Check HWND -----------------------------------------------------------------------------------------------------
124 | If !(CtrlHwnd := HWND + 0) || !DllCall("User32.dll\IsWindow", "UPtr", HWND, "UInt") {
125 | This.ErrorMsg := "Invalid parameter HWND: " . HWND
126 | Return False
127 | }
128 | If This.Attached.HasKey(HWND) {
129 | This.ErrorMsg := "Control " . HWND . " is already registered!"
130 | Return False
131 | }
132 | Hwnds := [CtrlHwnd]
133 | ; Check control's class ------------------------------------------------------------------------------------------
134 | Classes := ""
135 | WinGetClass, CtrlClass, ahk_id %CtrlHwnd%
136 | This.ErrorMsg := "Unsupported control class: " . CtrlClass
137 | If !ClassNames.HasKey(CtrlClass)
138 | Return False
139 | ControlGet, CtrlStyle, Style, , , ahk_id %CtrlHwnd%
140 | If (CtrlClass = "Edit")
141 | Classes := ["Edit", "Static"]
142 | Else If (CtrlClass = "Button") {
143 | IF (CtrlStyle & BS_RADIOBUTTON) || (CtrlStyle & BS_CHECKBOX)
144 | Classes := ["Static"]
145 | Else
146 | Return False
147 | }
148 | Else If (CtrlClass = "ComboBox") {
149 | VarSetCapacity(CBBI, 40 + (A_PtrSize * 3), 0)
150 | NumPut(40 + (A_PtrSize * 3), CBBI, 0, "UInt")
151 | DllCall("User32.dll\GetComboBoxInfo", "Ptr", CtrlHwnd, "Ptr", &CBBI)
152 | Hwnds.Insert(NumGet(CBBI, 40 + (A_PtrSize * 2, "UPtr")) + 0)
153 | Hwnds.Insert(Numget(CBBI, 40 + A_PtrSize, "UPtr") + 0)
154 | Classes := ["Edit", "Static", "ListBox"]
155 | }
156 | If !IsObject(Classes)
157 | Classes := [CtrlClass]
158 | ; Check background color -----------------------------------------------------------------------------------------
159 | If (BkColor <> "Trans")
160 | If !This.CheckBkColor(BkColor, Classes[1])
161 | Return False
162 | ; Check text color -----------------------------------------------------------------------------------------------
163 | If !This.CheckTxColor(TxColor)
164 | Return False
165 | ; Activate message handling on the first call for a class --------------------------------------------------------
166 | For I, V In Classes {
167 | If (This.HandledMessages[V] = 0)
168 | OnMessage(This.WM_CTLCOLOR[V], This.MessageHandler)
169 | This.HandledMessages[V] += 1
170 | }
171 | ; Store values for HWND ------------------------------------------------------------------------------------------
172 | If (BkColor = "Trans")
173 | Brush := This.NullBrush
174 | Else
175 | Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
176 | For I, V In Hwnds
177 | This.Attached[V] := {Brush: Brush, TxColor: TxColor, BkColor: BkColor, Classes: Classes, Hwnds: Hwnds}
178 | ; Redraw control -------------------------------------------------------------------------------------------------
179 | DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
180 | This.ErrorMsg := ""
181 | Return True
182 | }
183 | ; ===================================================================================================================
184 | ; Change Change control colors.
185 | ; Parameters: HWND - HWND of the GUI control
186 | ; BkColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
187 | ; ----------- Optional
188 | ; TxColor - HTML color name, 6-digit hexadecimal RGB value, or "" for default color
189 | ; Return values: On success - True
190 | ; On failure - False, CtlColors.ErrorMsg contains additional informations
191 | ; Remarks: If the control isn't registered yet, Add() is called instead internally.
192 | ; ===================================================================================================================
193 | Change(HWND, BkColor, TxColor := "") {
194 | ; Check HWND -----------------------------------------------------------------------------------------------------
195 | This.ErrorMsg := ""
196 | HWND += 0
197 | If !This.Attached.HasKey(HWND)
198 | Return This.Attach(HWND, BkColor, TxColor)
199 | CTL := This.Attached[HWND]
200 | ; Check BkColor --------------------------------------------------------------------------------------------------
201 | If (BkColor <> "Trans")
202 | If !This.CheckBkColor(BkColor, CTL.Classes[1])
203 | Return False
204 | ; Check TxColor ------------------------------------------------------------------------------------------------
205 | If !This.CheckTxColor(TxColor)
206 | Return False
207 | ; Store Colors ---------------------------------------------------------------------------------------------------
208 | If (BkColor <> CTL.BkColor) {
209 | If (CTL.Brush) {
210 | If (Ctl.Brush <> This.NullBrush)
211 | DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
212 | This.Attached[HWND].Brush := 0
213 | }
214 | If (BkColor = "Trans")
215 | Brush := This.NullBrush
216 | Else
217 | Brush := DllCall("Gdi32.dll\CreateSolidBrush", "UInt", BkColor, "UPtr")
218 | For I, V In CTL.Hwnds {
219 | This.Attached[V].Brush := Brush
220 | This.Attached[V].BkColor := BkColor
221 | }
222 | }
223 | For I, V In Ctl.Hwnds
224 | This.Attached[V].TxColor := TxColor
225 | This.ErrorMsg := ""
226 | DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
227 | Return True
228 | }
229 | ; ===================================================================================================================
230 | ; Detach Stop control coloring.
231 | ; Parameters: HWND - HWND of the GUI control
232 | ; Return values: On success - True
233 | ; On failure - False, CtlColors.ErrorMsg contains additional informations
234 | ; ===================================================================================================================
235 | Detach(HWND) {
236 | This.ErrorMsg := ""
237 | HWND += 0
238 | If This.Attached.HasKey(HWND) {
239 | CTL := This.Attached[HWND].Clone()
240 | If (CTL.Brush) && (CTL.Brush <> This.NullBrush)
241 | DllCall("Gdi32.dll\DeleteObject", "Prt", CTL.Brush)
242 | For I, V In CTL.Classes {
243 | If This.HandledMessages[V] > 0 {
244 | This.HandledMessages[V] -= 1
245 | If This.HandledMessages[V] = 0
246 | OnMessage(This.WM_CTLCOLOR[V], "")
247 | } }
248 | For I, V In CTL.Hwnds
249 | This.Attached.Remove(V, "")
250 | DllCall("User32.dll\InvalidateRect", "Ptr", HWND, "Ptr", 0, "Int", 1)
251 | CTL := ""
252 | Return True
253 | }
254 | This.ErrorMsg := "Control " . HWND . " is not registered!"
255 | Return False
256 | }
257 | ; ===================================================================================================================
258 | ; Free Stop coloring for all controls and free resources.
259 | ; Return values: Always True.
260 | ; ===================================================================================================================
261 | Free() {
262 | For K, V In This.Attached
263 | If (V.Brush) && (V.Brush <> This.NullBrush)
264 | DllCall("Gdi32.dll\DeleteObject", "Ptr", V.Brush)
265 | For K, V In This.HandledMessages
266 | If (V > 0) {
267 | OnMessage(This.WM_CTLCOLOR[K], "")
268 | This.HandledMessages[K] := 0
269 | }
270 | This.Attached := {}
271 | Return True
272 | }
273 | ; ===================================================================================================================
274 | ; IsAttached Check if the control is registered for coloring.
275 | ; Parameters: HWND - HWND of the GUI control
276 | ; Return values: On success - True
277 | ; On failure - False
278 | ; ===================================================================================================================
279 | IsAttached(HWND) {
280 | Return This.Attached.HasKey(HWND)
281 | }
282 | }
283 | ; ======================================================================================================================
284 | ; CtlColors_OnMessage
285 | ; This function handles CTLCOLOR messages. There's no reason to call it manually!
286 | ; ======================================================================================================================
287 | CtlColors_OnMessage(HDC, HWND) {
288 | Critical
289 | If CtlColors.IsAttached(HWND) {
290 | CTL := CtlColors.Attached[HWND]
291 | If (CTL.TxColor != "")
292 | DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", CTL.TxColor)
293 | If (CTL.BkColor = "Trans")
294 | DllCall("Gdi32.dll\SetBkMode", "Ptr", HDC, "UInt", 1) ; TRANSPARENT = 1
295 | Else
296 | DllCall("Gdi32.dll\SetBkColor", "Ptr", HDC, "UInt", CTL.BkColor)
297 | Return CTL.Brush
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/lib/Class_ImageButton.ahk:
--------------------------------------------------------------------------------
1 | ;Note from takanuva15: Not under MIT license. See license below for license details
2 | ;I have made small edits to this class to prevent warnings popping up about undeclared variables
3 |
4 | ; ======================================================================================================================
5 | ; Namespace: ImageButton
6 | ; Function: Create images and assign them to pushbuttons.
7 | ; Tested with: AHK 1.1.14.03 (A32/U32/U64)
8 | ; Tested on: Win 7 (x64)
9 | ; Change history: 1.4.00.00/2014-06-07/just me - fixed bug for button caption = "0", "000", etc.
10 | ; 1.3.00.00/2014-02-28/just me - added support for ARGB colors
11 | ; 1.2.00.00/2014-02-23/just me - added borders
12 | ; 1.1.00.00/2013-12-26/just me - added rounded and bicolored buttons
13 | ; 1.0.00.00/2013-12-21/just me - initial release
14 | ; How to use:
15 | ; 1. Create a push button (e.g. "Gui, Add, Button, vMyButton hwndHwndButton, Caption") using the 'Hwnd' option
16 | ; to get its HWND.
17 | ; 2. Call ImageButton.Create() passing two parameters:
18 | ; HWND - Button's HWND.
19 | ; Options* - variadic array containing up to 6 option arrays (see below).
20 | ; ---------------------------------------------------------------------------------------------------------------
21 | ; The index of each option object determines the corresponding button state on which the bitmap will be shown.
22 | ; MSDN defines 6 states (http://msdn.microsoft.com/en-us/windows/bb775975):
23 | ; PBS_NORMAL = 1
24 | ; PBS_HOT = 2
25 | ; PBS_PRESSED = 3
26 | ; PBS_DISABLED = 4
27 | ; PBS_DEFAULTED = 5
28 | ; PBS_STYLUSHOT = 6 <- used only on tablet computers (that's false for Windows Vista and 7, see below)
29 | ; If you don't want the button to be 'animated' on themed GUIs, just pass one option object with index 1.
30 | ; On Windows Vista and 7 themed bottons are 'animated' using the images of states 5 and 6 after clicked.
31 | ; ---------------------------------------------------------------------------------------------------------------
32 | ; Each option array may contain the following values:
33 | ; Index Value
34 | ; 1 Mode mandatory:
35 | ; 0 - unicolored or bitmap
36 | ; 1 - vertical bicolored
37 | ; 2 - horizontal bicolored
38 | ; 3 - vertical gradient
39 | ; 4 - horizontal gradient
40 | ; 5 - vertical gradient using StartColor at both borders and TargetColor at the center
41 | ; 6 - horizontal gradient using StartColor at both borders and TargetColor at the center
42 | ; 7 - 'raised' style
43 | ; 2 StartColor mandatory for Option[1], higher indices will inherit the value of Option[1], if omitted:
44 | ; - ARGB integer value (0xAARRGGBB) or HTML color name ("Red").
45 | ; - Path of an image file or HBITMAP handle for mode 0.
46 | ; 3 TargetColor mandatory for Option[1] if Mode > 0, ignored if Mode = 0. Higher indcices will inherit
47 | ; the color of Option[1], if omitted:
48 | ; - ARGB integer value (0xAARRGGBB) or HTML color name ("Red").
49 | ; 4 TextColor optional, if omitted, the default text color will be used for Option[1], higher indices
50 | ; will inherit the color of Option[1]:
51 | ; - ARGB integer value (0xAARRGGBB) or HTML color name ("Red").
52 | ; Default: 0xFF000000 (black)
53 | ; 5 Rounded optional:
54 | ; - Radius of the rounded corners in pixel; the letters 'H' and 'W' may be specified
55 | ; also to use the half of the button's height or width respectively.
56 | ; Default: 0 - not rounded
57 | ; 6 GuiColor optional, needed for rounded buttons if you've changed the GUI background color:
58 | ; - RGB integer value (0xRRGGBB) or HTML color name ("Red").
59 | ; Default: AHK default GUI background color
60 | ; 7 BorderColor optional, ignored for modes 0 (bitmap) and 7, color of the border:
61 | ; - RGB integer value (0xRRGGBB) or HTML color name ("Red").
62 | ; 8 BorderWidth optional, ignored for modes 0 (bitmap) and 7, width of the border in pixels:
63 | ; - Default: 1
64 | ; ---------------------------------------------------------------------------------------------------------------
65 | ; If the the button has a caption it will be drawn above the bitmap.
66 | ; Credits: THX tic for GDIP.AHK : http://www.autohotkey.com/forum/post-198949.html
67 | ; THX tkoi for ILBUTTON.AHK : http://www.autohotkey.com/forum/topic40468.html
68 | ; ======================================================================================================================
69 | ; This software is provided 'as-is', without any express or implied warranty.
70 | ; In no event will the authors be held liable for any damages arising from the use of this software.
71 | ; ======================================================================================================================
72 | ; ======================================================================================================================
73 | ; CLASS ImageButton()
74 | ; ======================================================================================================================
75 | Class ImageButton {
76 | ; ===================================================================================================================
77 | ; PUBLIC PROPERTIES =================================================================================================
78 | ; ===================================================================================================================
79 | Static DefGuiColor := "" ; default GUI color (read/write)
80 | Static DefTxtColor := "Black" ; default caption color (read/write)
81 | Static LastError := "" ; will contain the last error message, if any (readonly)
82 | ; ===================================================================================================================
83 | ; PRIVATE PROPERTIES ================================================================================================
84 | ; ===================================================================================================================
85 | Static BitMaps := []
86 | Static GDIPDll := 0
87 | Static GDIPToken := 0
88 | Static MaxOptions := 8
89 | ; HTML colors
90 | Static HTML := {BLACK: 0x000000, GRAY: 0x808080, SILVER: 0xC0C0C0, WHITE: 0xFFFFFF, MAROON: 0x800000
91 | , PURPLE: 0x800080, FUCHSIA: 0xFF00FF, RED: 0xFF0000, GREEN: 0x008000, OLIVE: 0x808000
92 | , YELLOW: 0xFFFF00, LIME: 0x00FF00, NAVY: 0x000080, TEAL: 0x008080, AQUA: 0x00FFFF, BLUE: 0x0000FF}
93 | ; Initialize
94 | Static ClassInit := ImageButton.InitClass()
95 | ; ===================================================================================================================
96 | ; PRIVATE METHODS ===================================================================================================
97 | ; ===================================================================================================================
98 | __New(P*) {
99 | Return False
100 | }
101 | ; ===================================================================================================================
102 | InitClass() {
103 | ; ----------------------------------------------------------------------------------------------------------------
104 | ; Get AHK's default GUI background color
105 | GuiColor := DllCall("User32.dll\GetSysColor", "Int", 15, "UInt") ; COLOR_3DFACE is used by AHK as default
106 | This.DefGuiColor := ((GuiColor >> 16) & 0xFF) | (GuiColor & 0x00FF00) | ((GuiColor & 0xFF) << 16)
107 | Return True
108 | }
109 | ; ===================================================================================================================
110 | GdiplusStartup() {
111 | This.GDIPDll := This.GDIPToken := 0
112 | If (This.GDIPDll := DllCall("Kernel32.dll\LoadLibrary", "Str", "Gdiplus.dll", "Ptr")) {
113 | VarSetCapacity(SI, 24, 0)
114 | Numput(1, SI, 0, "Int")
115 | GDIPToken := 0
116 | If !DllCall("Gdiplus.dll\GdiplusStartup", "PtrP", GDIPToken, "Ptr", &SI, "Ptr", 0)
117 | This.GDIPToken := GDIPToken
118 | Else
119 | This.GdiplusShutdown()
120 | }
121 | Return This.GDIPToken
122 | }
123 | ; ===================================================================================================================
124 | GdiplusShutdown() {
125 | If This.GDIPToken
126 | DllCall("Gdiplus.dll\GdiplusShutdown", "Ptr", This.GDIPToken)
127 | If This.GDIPDll
128 | DllCall("Kernel32.dll\FreeLibrary", "Ptr", This.GDIPDll)
129 | This.GDIPDll := This.GDIPToken := 0
130 | }
131 | ; ===================================================================================================================
132 | FreeBitmaps() {
133 | For I, HBITMAP In This.BitMaps
134 | DllCall("Gdi32.dll\DeleteObject", "Ptr", HBITMAP)
135 | This.BitMaps := []
136 | }
137 | ; ===================================================================================================================
138 | GetARGB(RGB) {
139 | ARGB := This.HTML.HasKey(RGB) ? This.HTML[RGB] : RGB
140 | Return (ARGB & 0xFF000000) = 0 ? 0xFF000000 | ARGB : ARGB
141 | }
142 | ; ===================================================================================================================
143 | PathAddRectangle(Path, X, Y, W, H) {
144 | Return DllCall("Gdiplus.dll\GdipAddPathRectangle", "Ptr", Path, "Float", X, "Float", Y, "Float", W, "Float", H)
145 | }
146 | ; ===================================================================================================================
147 | PathAddRoundedRect(Path, X1, Y1, X2, Y2, R) {
148 | D := (R * 2), X2 -= D, Y2 -= D
149 | DllCall("Gdiplus.dll\GdipAddPathArc"
150 | , "Ptr", Path, "Float", X1, "Float", Y1, "Float", D, "Float", D, "Float", 180, "Float", 90)
151 | DllCall("Gdiplus.dll\GdipAddPathArc"
152 | , "Ptr", Path, "Float", X2, "Float", Y1, "Float", D, "Float", D, "Float", 270, "Float", 90)
153 | DllCall("Gdiplus.dll\GdipAddPathArc"
154 | , "Ptr", Path, "Float", X2, "Float", Y2, "Float", D, "Float", D, "Float", 0, "Float", 90)
155 | DllCall("Gdiplus.dll\GdipAddPathArc"
156 | , "Ptr", Path, "Float", X1, "Float", Y2, "Float", D, "Float", D, "Float", 90, "Float", 90)
157 | Return DllCall("Gdiplus.dll\GdipClosePathFigure", "Ptr", Path)
158 | }
159 | ; ===================================================================================================================
160 | SetRect(ByRef Rect, X1, Y1, X2, Y2) {
161 | VarSetCapacity(Rect, 16, 0)
162 | NumPut(X1, Rect, 0, "Int"), NumPut(Y1, Rect, 4, "Int")
163 | NumPut(X2, Rect, 8, "Int"), NumPut(Y2, Rect, 12, "Int")
164 | Return True
165 | }
166 | ; ===================================================================================================================
167 | SetRectF(ByRef Rect, X, Y, W, H) {
168 | VarSetCapacity(Rect, 16, 0)
169 | NumPut(X, Rect, 0, "Float"), NumPut(Y, Rect, 4, "Float")
170 | NumPut(W, Rect, 8, "Float"), NumPut(H, Rect, 12, "Float")
171 | Return True
172 | }
173 | ; ===================================================================================================================
174 | SetError(Msg) {
175 | This.FreeBitmaps()
176 | This.GdiplusShutdown()
177 | This.LastError := Msg
178 | Return False
179 | }
180 | ; ===================================================================================================================
181 | ; PUBLIC METHODS ====================================================================================================
182 | ; ===================================================================================================================
183 | Create(HWND, Options*) {
184 | ; Windows constants
185 | Static BCM_SETIMAGELIST := 0x1602
186 | , BS_CHECKBOX := 0x02, BS_RADIOBUTTON := 0x04, BS_GROUPBOX := 0x07, BS_AUTORADIOBUTTON := 0x09
187 | , BS_LEFT := 0x0100, BS_RIGHT := 0x0200, BS_CENTER := 0x0300, BS_TOP := 0x0400, BS_BOTTOM := 0x0800
188 | , BS_VCENTER := 0x0C00, BS_BITMAP := 0x0080
189 | , BUTTON_IMAGELIST_ALIGN_LEFT := 0, BUTTON_IMAGELIST_ALIGN_RIGHT := 1, BUTTON_IMAGELIST_ALIGN_CENTER := 4
190 | , ILC_COLOR32 := 0x20
191 | , OBJ_BITMAP := 7
192 | , RCBUTTONS := BS_CHECKBOX | BS_RADIOBUTTON | BS_AUTORADIOBUTTON
193 | , SA_LEFT := 0x00, SA_CENTER := 0x01, SA_RIGHT := 0x02
194 | , WM_GETFONT := 0x31
195 | ; ----------------------------------------------------------------------------------------------------------------
196 | This.LastError := ""
197 | ; ----------------------------------------------------------------------------------------------------------------
198 | ; Check HWND
199 | If !DllCall("User32.dll\IsWindow", "Ptr", HWND)
200 | Return This.SetError("Invalid parameter HWND!")
201 | ; ----------------------------------------------------------------------------------------------------------------
202 | ; Check Options
203 | If !(IsObject(Options)) || (Options.MinIndex() <> 1) || (Options.MaxIndex() > This.MaxOptions)
204 | Return This.SetError("Invalid parameter Options!")
205 | ; ----------------------------------------------------------------------------------------------------------------
206 | ; Get and check control's class and styles
207 | WinGetClass, BtnClass, ahk_id %HWND%
208 | ControlGet, BtnStyle, Style, , , ahk_id %HWND%
209 | If (BtnClass != "Button") || ((BtnStyle & 0xF ^ BS_GROUPBOX) = 0) || ((BtnStyle & RCBUTTONS) > 1)
210 | Return This.SetError("The control must be a pushbutton!")
211 | ; ----------------------------------------------------------------------------------------------------------------
212 | ; Load GdiPlus
213 | If !This.GdiplusStartup()
214 | Return This.SetError("GDIPlus could not be started!")
215 | ; ----------------------------------------------------------------------------------------------------------------
216 | ; Get the button's font
217 | GDIPFont := 0
218 | HFONT := DllCall("User32.dll\SendMessage", "Ptr", HWND, "UInt", WM_GETFONT, "Ptr", 0, "Ptr", 0, "Ptr")
219 | DC := DllCall("User32.dll\GetDC", "Ptr", HWND, "Ptr")
220 | DllCall("Gdi32.dll\SelectObject", "Ptr", DC, "Ptr", HFONT)
221 | PFONT :=
222 | DllCall("Gdiplus.dll\GdipCreateFontFromDC", "Ptr", DC, "PtrP", PFONT)
223 | DllCall("User32.dll\ReleaseDC", "Ptr", HWND, "Ptr", DC)
224 | If !(PFONT)
225 | Return This.SetError("Couldn't get button's font!")
226 | ; ----------------------------------------------------------------------------------------------------------------
227 | ; Get the button's rectangle
228 | VarSetCapacity(RECT, 16, 0)
229 | If !DllCall("User32.dll\GetWindowRect", "Ptr", HWND, "Ptr", &RECT)
230 | Return This.SetError("Couldn't get button's rectangle!")
231 | BtnW := NumGet(RECT, 8, "Int") - NumGet(RECT, 0, "Int")
232 | BtnH := NumGet(RECT, 12, "Int") - NumGet(RECT, 4, "Int")
233 | ; ----------------------------------------------------------------------------------------------------------------
234 | ; Get the button's caption
235 | ControlGetText, BtnCaption, , ahk_id %HWND%
236 | If (ErrorLevel)
237 | Return This.SetError("Couldn't get button's caption!")
238 | ; ----------------------------------------------------------------------------------------------------------------
239 | ; Create the bitmap(s)
240 | This.BitMaps := []
241 | For Index, Option In Options {
242 | If !IsObject(Option)
243 | Continue
244 | BkgColor1 := BkgColor2 := TxtColor := Mode := Rounded := GuiColor := Image := ""
245 | ; Replace omitted options with the values of Options.1
246 | Loop, % This.MaxOptions {
247 | If (Option[A_Index] = "")
248 | Option[A_Index] := Options.1[A_Index]
249 | }
250 | ; -------------------------------------------------------------------------------------------------------------
251 | ; Check option values
252 | ; Mode
253 | Mode := SubStr(Option.1, 1 ,1)
254 | If !InStr("0123456789", Mode)
255 | Return This.SetError("Invalid value for Mode in Options[" . Index . "]!")
256 | ; StartColor & TargetColor
257 | If (Mode = 0)
258 | && (FileExist(Option.2) || (DllCall("Gdi32.dll\GetObjectType", "Ptr", Option.2, "UInt") = OBJ_BITMAP))
259 | Image := Option.2
260 | Else {
261 | If !(Option.2 + 0) && !This.HTML.HasKey(Option.2)
262 | Return This.SetError("Invalid value for StartColor in Options[" . Index . "]!")
263 | BkgColor1 := This.GetARGB(Option.2)
264 | If (Option.3 = "")
265 | Option.3 := Option.2
266 | If !(Option.3 + 0) && !This.HTML.HasKey(Option.3)
267 | Return This.SetError("Invalid value for TargetColor in Options[" . Index . "]!")
268 | BkgColor2 := This.GetARGB(Option.3)
269 | }
270 | ; TextColor
271 | If (Option.4 = "")
272 | Option.4 := This.DefTxtColor
273 | If !(Option.4 + 0) && !This.HTML.HasKey(Option.4)
274 | Return This.SetError("Invalid value for TxtColor in Options[" . Index . "]!")
275 | TxtColor := This.GetARGB(Option.4)
276 | ; Rounded
277 | Rounded := Option.5
278 | If (Rounded = "H")
279 | Rounded := BtnH * 0.5
280 | If (Rounded = "W")
281 | Rounded := BtnW * 0.5
282 | If !(Rounded + 0)
283 | Rounded := 0
284 | ; GuiColor
285 | If (Option.6 = "")
286 | Option.6 := This.DefGuiColor
287 | If !(Option.6 + 0) && !This.HTML.HasKey(Option.6)
288 | Return This.SetError("Invalid value for GuiColor in Options[" . Index . "]!")
289 | GuiColor := This.GetARGB(Option.6)
290 | ; BorderColor
291 | BorderColor := ""
292 | If (Option.7 <> "") {
293 | If !(Option.7 + 0) && !This.HTML.HasKey(Option.7)
294 | Return This.SetError("Invalid value for BorderColor in Options[" . Index . "]!")
295 | BorderColor := 0xFF000000 | This.GetARGB(Option.7) ; BorderColor must be always opaque
296 | }
297 | ; BorderWidth
298 | BorderWidth := Option.8 ? Option.8 : 1
299 | ; -------------------------------------------------------------------------------------------------------------
300 | ; Create a GDI+ bitmap
301 | PBITMAP :=
302 | DllCall("Gdiplus.dll\GdipCreateBitmapFromScan0", "Int", BtnW, "Int", BtnH, "Int", 0
303 | , "UInt", 0x26200A, "Ptr", 0, "PtrP", PBITMAP)
304 | ; Get the pointer to its graphics
305 | PGRAPHICS :=
306 | DllCall("Gdiplus.dll\GdipGetImageGraphicsContext", "Ptr", PBITMAP, "PtrP", PGRAPHICS)
307 | ; Quality settings
308 | DllCall("Gdiplus.dll\GdipSetSmoothingMode", "Ptr", PGRAPHICS, "UInt", 4)
309 | DllCall("Gdiplus.dll\GdipSetInterpolationMode", "Ptr", PGRAPHICS, "Int", 7)
310 | DllCall("Gdiplus.dll\GdipSetCompositingQuality", "Ptr", PGRAPHICS, "UInt", 4)
311 | DllCall("Gdiplus.dll\GdipSetRenderingOrigin", "Ptr", PGRAPHICS, "Int", 0, "Int", 0)
312 | DllCall("Gdiplus.dll\GdipSetPixelOffsetMode", "Ptr", PGRAPHICS, "UInt", 4)
313 | ; Clear the background
314 | DllCall("Gdiplus.dll\GdipGraphicsClear", "Ptr", PGRAPHICS, "UInt", GuiColor)
315 | ; Create the image
316 | If (Image = "") { ; Create a BitMap based on the specified colors
317 | PathX := PathY := 0, PathW := BtnW, PathH := BtnH
318 | ; Create a GraphicsPath
319 | PPATH :=
320 | DllCall("Gdiplus.dll\GdipCreatePath", "UInt", 0, "PtrP", PPATH)
321 | If (Rounded < 1) ; the path is a rectangular rectangle
322 | This.PathAddRectangle(PPATH, PathX, PathY, PathW, PathH)
323 | Else ; the path is a rounded rectangle
324 | This.PathAddRoundedRect(PPATH, PathX, PathY, PathW, PathH, Rounded)
325 | ; If BorderColor and BorderWidth are specified, 'draw' the border (not for Mode 7)
326 | If (BorderColor <> "") && (BorderWidth > 0) && (Mode <> 7) {
327 | ; Create a SolidBrush
328 | PBRUSH :=
329 | DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", BorderColor, "PtrP", PBRUSH)
330 | ; Fill the path
331 | DllCall("Gdiplus.dll\GdipFillPath", "Ptr", PGRAPHICS, "Ptr", PBRUSH, "Ptr", PPATH)
332 | ; Free the brush
333 | DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)
334 | ; Reset the path
335 | DllCall("Gdiplus.dll\GdipResetPath", "Ptr", PPATH)
336 | ; Add a new 'inner' path
337 | PathX := PathY := BorderWidth, PathW -= BorderWidth, PathH -= BorderWidth, Rounded -= BorderWidth
338 | If (Rounded < 1) ; the path is a rectangular rectangle
339 | This.PathAddRectangle(PPATH, PathX, PathY, PathW - PathX, PathH - PathY)
340 | Else ; the path is a rounded rectangle
341 | This.PathAddRoundedRect(PPATH, PathX, PathY, PathW, PathH, Rounded)
342 | ; If a BorderColor has been drawn, BkgColors must be opaque
343 | BkgColor1 := 0xFF000000 | BkgColor1
344 | BkgColor2 := 0xFF000000 | BkgColor2
345 | }
346 | PathW -= PathX
347 | PathH -= PathY
348 | If (Mode = 0) { ; the background is unicolored
349 | ; Create a SolidBrush
350 | PBRUSH :=
351 | DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", BkgColor1, "PtrP", PBRUSH)
352 | ; Fill the path
353 | DllCall("Gdiplus.dll\GdipFillPath", "Ptr", PGRAPHICS, "Ptr", PBRUSH, "Ptr", PPATH)
354 | }
355 | Else If (Mode = 1) || (Mode = 2) { ; the background is bicolored
356 | ; Create a LineGradientBrush
357 | This.SetRectF(RECTF, PathX, PathY, PathW, PathH)
358 | DllCall("Gdiplus.dll\GdipCreateLineBrushFromRect", "Ptr", &RECTF
359 | , "UInt", BkgColor1, "UInt", BkgColor2, "Int", Mode & 1, "Int", 3, "PtrP", PBRUSH)
360 | DllCall("Gdiplus.dll\GdipSetLineGammaCorrection", "Ptr", PBRUSH, "Int", 1)
361 | ; Set up colors and positions
362 | This.SetRect(COLORS, BkgColor1, BkgColor1, BkgColor2, BkgColor2) ; sorry for function misuse
363 | This.SetRectF(POSITIONS, 0, 0.5, 0.5, 1) ; sorry for function misuse
364 | DllCall("Gdiplus.dll\GdipSetLinePresetBlend", "Ptr", PBRUSH
365 | , "Ptr", &COLORS, "Ptr", &POSITIONS, "Int", 4)
366 | ; Fill the path
367 | DllCall("Gdiplus.dll\GdipFillPath", "Ptr", PGRAPHICS, "Ptr", PBRUSH, "Ptr", PPATH)
368 | }
369 | Else If (Mode >= 3) && (Mode <= 6) { ; the background is a gradient
370 | ; Determine the brush's width/height
371 | W := Mode = 6 ? PathW / 2 : PathW ; horizontal
372 | H := Mode = 5 ? PathH / 2 : PathH ; vertical
373 | ; Create a LineGradientBrush
374 | This.SetRectF(RECTF, PathX, PathY, W, H)
375 | DllCall("Gdiplus.dll\GdipCreateLineBrushFromRect", "Ptr", &RECTF
376 | , "UInt", BkgColor1, "UInt", BkgColor2, "Int", Mode & 1, "Int", 3, "PtrP", PBRUSH)
377 | DllCall("Gdiplus.dll\GdipSetLineGammaCorrection", "Ptr", PBRUSH, "Int", 1)
378 | ; Fill the path
379 | DllCall("Gdiplus.dll\GdipFillPath", "Ptr", PGRAPHICS, "Ptr", PBRUSH, "Ptr", PPATH)
380 | }
381 | Else { ; raised mode
382 | DllCall("Gdiplus.dll\GdipCreatePathGradientFromPath", "Ptr", PPATH, "PtrP", PBRUSH)
383 | ; Set Gamma Correction
384 | DllCall("Gdiplus.dll\GdipSetPathGradientGammaCorrection", "Ptr", PBRUSH, "UInt", 1)
385 | ; Set surround and center colors
386 | VarSetCapacity(ColorArray, 4, 0)
387 | NumPut(BkgColor1, ColorArray, 0, "UInt")
388 | DllCall("Gdiplus.dll\GdipSetPathGradientSurroundColorsWithCount", "Ptr", PBRUSH, "Ptr", &ColorArray
389 | , "IntP", 1)
390 | DllCall("Gdiplus.dll\GdipSetPathGradientCenterColor", "Ptr", PBRUSH, "UInt", BkgColor2)
391 | ; Set the FocusScales
392 | FS := (BtnH < BtnW ? BtnH : BtnW) / 3
393 | XScale := (BtnW - FS) / BtnW
394 | YScale := (BtnH - FS) / BtnH
395 | DllCall("Gdiplus.dll\GdipSetPathGradientFocusScales", "Ptr", PBRUSH, "Float", XScale, "Float", YScale)
396 | ; Fill the path
397 | DllCall("Gdiplus.dll\GdipFillPath", "Ptr", PGRAPHICS, "Ptr", PBRUSH, "Ptr", PPATH)
398 | }
399 | ; Free resources
400 | DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)
401 | DllCall("Gdiplus.dll\GdipDeletePath", "Ptr", PPATH)
402 | } Else { ; Create a bitmap from HBITMAP or file
403 | If (Image + 0)
404 | DllCall("Gdiplus.dll\GdipCreateBitmapFromHBITMAP", "Ptr", Image, "Ptr", 0, "PtrP", PBM)
405 | Else
406 | DllCall("Gdiplus.dll\GdipCreateBitmapFromFile", "WStr", Image, "PtrP", PBM)
407 | ; Draw the bitmap
408 | DllCall("Gdiplus.dll\GdipDrawImageRectI", "Ptr", PGRAPHICS, "Ptr", PBM, "Int", 0, "Int", 0
409 | , "Int", BtnW, "Int", BtnH)
410 | ; Free the bitmap
411 | DllCall("Gdiplus.dll\GdipDisposeImage", "Ptr", PBM)
412 | }
413 | ; -------------------------------------------------------------------------------------------------------------
414 | ; Draw the caption
415 | If (BtnCaption <> "") {
416 | ; Create a StringFormat object
417 | HFORMAT :=
418 | DllCall("Gdiplus.dll\GdipStringFormatGetGenericTypographic", "PtrP", HFORMAT)
419 | ; Text color
420 | DllCall("Gdiplus.dll\GdipCreateSolidFill", "UInt", TxtColor, "PtrP", PBRUSH)
421 | ; Horizontal alignment
422 | HALIGN := (BtnStyle & BS_CENTER) = BS_CENTER ? SA_CENTER
423 | : (BtnStyle & BS_CENTER) = BS_RIGHT ? SA_RIGHT
424 | : (BtnStyle & BS_CENTER) = BS_Left ? SA_LEFT
425 | : SA_CENTER
426 | DllCall("Gdiplus.dll\GdipSetStringFormatAlign", "Ptr", HFORMAT, "Int", HALIGN)
427 | ; Vertical alignment
428 | VALIGN := (BtnStyle & BS_VCENTER) = BS_TOP ? 0
429 | : (BtnStyle & BS_VCENTER) = BS_BOTTOM ? 2
430 | : 1
431 | DllCall("Gdiplus.dll\GdipSetStringFormatLineAlign", "Ptr", HFORMAT, "Int", VALIGN)
432 | ; Set render quality to system default
433 | DllCall("Gdiplus.dll\GdipSetTextRenderingHint", "Ptr", PGRAPHICS, "Int", 0)
434 | ; Set the text's rectangle
435 | VarSetCapacity(RECT, 16, 0)
436 | NumPut(BtnW, RECT, 8, "Float")
437 | NumPut(BtnH, RECT, 12, "Float")
438 | ; Draw the text
439 | DllCall("Gdiplus.dll\GdipDrawString", "Ptr", PGRAPHICS, "WStr", BtnCaption, "Int", -1
440 | , "Ptr", PFONT, "Ptr", &RECT, "Ptr", HFORMAT, "Ptr", PBRUSH)
441 | }
442 | ; -------------------------------------------------------------------------------------------------------------
443 | ; Create a HBITMAP handle from the bitmap and add it to the array
444 | HBITMAP :=
445 | DllCall("Gdiplus.dll\GdipCreateHBITMAPFromBitmap", "Ptr", PBITMAP, "PtrP", HBITMAP, "UInt", 0X00FFFFFF)
446 | This.BitMaps[Index] := HBITMAP
447 | ; Free resources
448 | DllCall("Gdiplus.dll\GdipDisposeImage", "Ptr", PBITMAP)
449 | DllCall("Gdiplus.dll\GdipDeleteBrush", "Ptr", PBRUSH)
450 | DllCall("Gdiplus.dll\GdipDeleteStringFormat", "Ptr", HFORMAT)
451 | DllCall("Gdiplus.dll\GdipDeleteGraphics", "Ptr", PGRAPHICS)
452 | ; Add the bitmap to the array
453 | }
454 | ; Now free the font object
455 | DllCall("Gdiplus.dll\GdipDeleteFont", "Ptr", PFONT)
456 | ; ----------------------------------------------------------------------------------------------------------------
457 | ; Create the ImageList
458 | HIL := DllCall("Comctl32.dll\ImageList_Create"
459 | , "UInt", BtnW, "UInt", BtnH, "UInt", ILC_COLOR32, "Int", 6, "Int", 0, "Ptr")
460 | Loop, % (This.BitMaps.MaxIndex() > 1 ? 6 : 1) {
461 | HBITMAP := This.BitMaps.HasKey(A_Index) ? This.BitMaps[A_Index] : This.BitMaps.1
462 | DllCall("Comctl32.dll\ImageList_Add", "Ptr", HIL, "Ptr", HBITMAP, "Ptr", 0)
463 | }
464 | ; Create a BUTTON_IMAGELIST structure
465 | VarSetCapacity(BIL, 20 + A_PtrSize, 0)
466 | NumPut(HIL, BIL, 0, "Ptr")
467 | Numput(BUTTON_IMAGELIST_ALIGN_CENTER, BIL, A_PtrSize + 16, "UInt")
468 | ; Hide buttons's caption
469 | ControlSetText, , , ahk_id %HWND%
470 | Control, Style, +%BS_BITMAP%, , ahk_id %HWND%
471 | ; Assign the ImageList to the button
472 | SendMessage, %BCM_SETIMAGELIST%, 0, 0, , ahk_id %HWND%
473 | SendMessage, %BCM_SETIMAGELIST%, 0, % &BIL, , ahk_id %HWND%
474 | ; Free the bitmaps
475 | This.FreeBitmaps()
476 | ; ----------------------------------------------------------------------------------------------------------------
477 | ; All done successfully
478 | This.GdiplusShutdown()
479 | Return True
480 | }
481 | ; ===================================================================================================================
482 | ; Set the default GUI color
483 | SetGuiColor(GuiColor) {
484 | ; GuiColor - RGB integer value (0xRRGGBB) or HTML color name ("Red").
485 | If !(GuiColor + 0) && !This.HTML.HasKey(GuiColor)
486 | Return False
487 | This.DefGuiColor := (This.HTML.HasKey(GuiColor) ? This.HTML[GuiColor] : GuiColor) & 0xFFFFFF
488 | Return True
489 | }
490 | ; ===================================================================================================================
491 | ; Set the default text color
492 | SetTxtColor(TxtColor) {
493 | ; TxtColor - RGB integer value (0xRRGGBB) or HTML color name ("Red").
494 | If !(TxtColor + 0) && !This.HTML.HasKey(TxtColor)
495 | Return False
496 | This.DefTxtColor := (This.HTML.HasKey(TxtColor) ? This.HTML[TxtColor] : TxtColor) & 0xFFFFFF
497 | Return True
498 | }
499 | }
--------------------------------------------------------------------------------
/lib/Class_LB_AdjustHeight.ahk:
--------------------------------------------------------------------------------
1 | ;;License note by takanuva15: The 2 functions below this line is derived from a post by "SKAN" at (https://autohotkey.com/board/topic/33383-change-1-line-in-listbox-using-guicontrol/) and is NOT under the MIT license provided in the root directory of this repository.
2 | LB_InsertItemAtIndex(HListBox, item, index) {
3 | static LB_INSERTSTRING := 0x181
4 | SendMessage, %LB_INSERTSTRING%, % index - 1, &item, , ahk_id %HListBox%
5 | Return ErrorLevel
6 | }
7 |
8 | LB_DeleteItem(HListBox, index) {
9 | static LB_DELETESTRING := 0x182
10 | SendMessage, %LB_DELETESTRING%, % index - 1, 0, , ahk_id %HListBox%
11 | Return ErrorLevel
12 | }
13 |
14 | ;;License note by takanuva15: The code below this line is authored by "Just Me" (https://autohotkey.com/board/topic/89793-set-height-of-listbox-rows/#entry568445) and is NOT under the MIT license provided in the root directory of this repository. Please see the code comments for information on its license.
15 | LB_AdjustItemHeight(HListBox, Adjust) {
16 | Return LB_SetItemHeight(HListBox, LB_GetItemHeight(HListBox) + Adjust)
17 | }
18 |
19 | LB_GetItemHeight(HListBox) {
20 | Static LB_GETITEMHEIGHT := 0x01A1
21 | SendMessage, %LB_GETITEMHEIGHT%, 0, 0, , ahk_id %HListBox%
22 | Return ErrorLevel
23 | }
24 |
25 | LB_SetItemHeight(HListBox, NewHeight) {
26 | Static LB_SETITEMHEIGHT := 0x01A0
27 | SendMessage, %LB_SETITEMHEIGHT%, 0, %NewHeight%, , ahk_id %HListBox%
28 | WinSet, Redraw, , ahk_id %HListBox%
29 | Return ErrorLevel
30 | }
31 |
32 | ;;This function is derived from the LB_InsertItemAtIndex function above. It is under MIT license except for the LB_InsertItemAtIndex function itself.
33 | PopulateLBFromArray(HListBox, arr) {
34 | GuiControl, -Redraw, HListBox
35 | for index, element in arr {
36 | LB_InsertItemAtIndex(HListBox, element, index)
37 | }
38 | GuiControl, +Redraw, HListBox
39 | }
--------------------------------------------------------------------------------
/lib/Gui_Functions.ahk:
--------------------------------------------------------------------------------
1 | GetControlValue(hWnd) {
2 | GuiControlGet, tmp,, %hWnd%
3 | return tmp
4 | }
5 |
6 | ;This function came from https://autohotkey.com/board/topic/85172-solved-gui-width-height/. Not under MIT license
7 | GetGuiSize(hwnd, ByRef w, ByRef h) {
8 | VarSetCapacity(rc, 16)
9 | DllCall("GetClientRect", "uint", hwnd, "uint", &rc)
10 | w := NumGet(rc, 8, "int"), h := NumGet(rc, 12, "int")
11 | }
--------------------------------------------------------------------------------
/main.ahk:
--------------------------------------------------------------------------------
1 | ;[MenuClip Clipboard Manager]
2 |
3 | #NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
4 | #Warn ; Enable warnings to assist with detecting common errors.
5 | SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
6 | SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
7 | #SingleInstance, Force
8 |
9 | VERSION := "v1.15.2"
10 |
11 | #Include
12 | #Include
13 | #Include
14 | #Include
15 | #Include src\MenuClip.ahk
16 |
17 | ;configManager := new MenuClip.Config.ConfigManager(VERSION)
18 |
19 | clipManager := new MenuClip.Controller.ClipManager()
20 |
21 | trayManager := new MenuClip.Controller.TrayManager(VERSION, clipManager)
22 | trayManager.configureTrayTooltip()
23 | trayManager.configureTrayOptions()
24 |
25 | clipManager.monitorClipboardChanges()
26 |
27 | showMenu := ObjBindMethod(clipManager, "showContextMenu")
28 | Hotkey, % clipManager.configManager.getShowMenuHotkey(), % showMenu
29 |
30 | return
31 |
--------------------------------------------------------------------------------
/resources/menuclip_demo_dark_theme.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takanuva15/MenuClip/490c5ad84bed20f1855db4edc6640a311e8297a7/resources/menuclip_demo_dark_theme.jpg
--------------------------------------------------------------------------------
/resources/menuclip_demo_final.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/takanuva15/MenuClip/490c5ad84bed20f1855db4edc6640a311e8297a7/resources/menuclip_demo_final.gif
--------------------------------------------------------------------------------
/src/MenuClip.ahk:
--------------------------------------------------------------------------------
1 | ;Container class for the classes that MenuClip directly uses
2 | class MenuClip {
3 | #Include %A_ScriptDir%\src\config\Config.ahk
4 | #Include %A_ScriptDir%\src\controller\Controller.ahk
5 | #Include %A_ScriptDir%\src\model\Model.ahk
6 | #Include %A_ScriptDir%\src\view\View.ahk
7 | }
8 |
--------------------------------------------------------------------------------
/src/config/Config.ahk:
--------------------------------------------------------------------------------
1 | ;Container class for the model classes that MenuClip uses
2 | class Config {
3 | #Include %A_ScriptDir%\src\config\ConfigManager.ahk
4 | #Include %A_ScriptDir%\src\config\ConfigManagerGui.ahk
5 | }
--------------------------------------------------------------------------------
/src/config/ConfigGuiGeneralOptions.ahk:
--------------------------------------------------------------------------------
1 | ;Handles configuration file reading & writing
2 | class ConfigGuiGeneralOptions {
3 | static configManager
4 | static CONFIG_HANDLE_HOTKEY
5 | static CONFIG_HANDLE_MAX_CLIPS_TO_STORE
6 | static CONFIG_HANDLE_MAX_WIDTH
7 | static CONFIG_HANDLE_MAX_HEIGHT
8 | static CONFIG_HANDLE_ALT_PASTE_APPS
9 | static CONFIG_HANDLE_THEME
10 | static CONFIG_HANDLE_CONV_SPEC_CHAR
11 | __New(configManager) {
12 | this.configManager := configManager
13 | }
14 |
15 | addAllOptions() {
16 | this.addHotkeyOpt()
17 | this.addMaxClipsToStoreOpt()
18 | this.addMaxWidth()
19 | this.addMaxHeight()
20 | this.addAltPasteAppsOpt()
21 | this.addConvSpecCharOpt()
22 | }
23 |
24 | addHotkeyOpt() {
25 | Gui EditConfig:Add, Text, w170 Section, Hotkey:
26 | Gui EditConfig:Add, Edit, x+5 yp-2 w100 h17 hWndShowMenuHotkey, % this.configManager.getShowMenuHotkey()
27 | this.CONFIG_HANDLE_HOTKEY := ShowMenuHotkey
28 | }
29 |
30 | addMaxClipsToStoreOpt() {
31 | Gui EditConfig:Add, Text, xs y+10 w170, Number of clips to store:
32 | Gui EditConfig:Add, Edit, x+5 yp-2 w32 h17 hWndMaxClipsToStore, % this.configManager.getMaxClipsToStore()
33 | this.CONFIG_HANDLE_MAX_CLIPS_TO_STORE := MaxClipsToStore
34 | }
35 |
36 | addMaxWidth() {
37 | Gui EditConfig:Add, Text, xs y+10 w170, Max width:
38 | Gui EditConfig:Add, Edit, x+5 yp-2 w32 h17 hWndMaxWidth, % this.configManager.getMaxWidth()
39 | this.CONFIG_HANDLE_MAX_WIDTH := MaxWidth
40 | }
41 |
42 | addMaxHeight() {
43 | Gui EditConfig:Add, Text, xs y+10 w170, Max height (# rows):
44 | Gui EditConfig:Add, Edit, x+5 yp-2 w32 h17 hWndMaxHeight, % this.configManager.getMaxHeight()
45 | this.CONFIG_HANDLE_MAX_HEIGHT := MaxHeight
46 | }
47 |
48 | addAltPasteAppsOpt() {
49 | Gui EditConfig:Add, Text, xs y+10 w170, App exes that use Shift+Ins pasting: (comma-separated)
50 | Gui EditConfig:Add, Edit, x+5 yp-2 w100 r2 hWndAltPasteApps, % this.configManager.getAltPasteApps()
51 | this.CONFIG_HANDLE_ALT_PASTE_APPS := AltPasteApps
52 | }
53 |
54 | addConvSpecCharOpt() {
55 | Gui EditConfig:Add, Text, xs y+10 w170, Convert tabs and newlines to [\t] and [\n] in clips view
56 | Gui EditConfig:Add, CheckBox, % "x+5 yp-2 w100 r2 hWndConvSpecChar Checked" this.configManager.getConvSpecChar()
57 | this.CONFIG_HANDLE_CONV_SPEC_CHAR := ConvSpecChar
58 | }
59 |
60 | saveConfigs() {
61 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_HOTKEY, GetControlValue(this.CONFIG_HANDLE_HOTKEY))
62 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_MAX_CLIPS_TO_STORE, GetControlValue(this.CONFIG_HANDLE_MAX_CLIPS_TO_STORE))
63 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_MAX_WIDTH, GetControlValue(this.CONFIG_HANDLE_MAX_WIDTH))
64 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_MAX_HEIGHT, GetControlValue(this.CONFIG_HANDLE_MAX_HEIGHT))
65 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_ALT_PASTE_APPS, GetControlValue(this.CONFIG_HANDLE_ALT_PASTE_APPS))
66 | this.configManager.writeConfigToFile(this.configManager.CONFIG_NAME_CONV_SPEC_CHAR, GetControlValue(this.CONFIG_HANDLE_CONV_SPEC_CHAR))
67 | }
68 | }
--------------------------------------------------------------------------------
/src/config/ConfigGuiThemeOptions.ahk:
--------------------------------------------------------------------------------
1 | ;Handles configuration file reading & writing
2 | class ConfigGuiThemeOptions {
3 | static configManager
4 | static themes, themeStyle
5 | static CONFIG_HANDLE_THEME
6 | static CONFIG_HANDLE_AUTO_THEME_GROUPBOX
7 | static HR_OPTIONS := "01|02|03|04|05|06|07|08|09|10|11|12"
8 | static MINUTE_OPTIONS
9 | static CONFIG_HANDLE_DARK_START_TEXT, CONFIG_HANDLE_DARK_START_HR, CONFIG_HANDLE_DARK_START_MIN, CONFIG_HANDLE_DARK_START_PM
10 | static CONFIG_HANDLE_DARK_STOP_TEXT, CONFIG_HANDLE_DARK_STOP_HR, CONFIG_HANDLE_DARK_STOP_MIN, CONFIG_HANDLE_DARK_STOP_PM
11 | __New(configManager) {
12 | this.configManager := configManager
13 | this.configThemeManager := configManager.configThemeManager
14 | this.themeStyle := this.configThemeManager.CONFIG_VAL_THEME
15 | this.generateMinuteOptions()
16 | }
17 |
18 | addAllThemeOptions() {
19 | this.addThemeOpt()
20 | this.addAutoThemeOpts()
21 | this.disableAutoThemeConfigBySelectedTheme()
22 | }
23 |
24 | addThemeOpt() {
25 | this.themes := {"light":1, "dark":2, "auto":3}
26 | Gui EditConfig:Add, Text, xs y+10 w170, Theme:
27 | Gui EditConfig:Add, DDL, % "x+5 yp-2 w50 hWndMenuTheme Choose" this.themes[this.themeStyle], light|dark|auto
28 | if(this.configManager.getTheme() = "dark") {
29 | CtlColors.Attach(MenuTheme, "43474A","CCCCCC")
30 | }
31 | this.CONFIG_HANDLE_THEME := MenuTheme
32 | disableAutoThemeConfigBySelectedThemeFn := ObjBindMethod(this, "disableAutoThemeConfigBySelectedTheme")
33 | GuiControl +g, %MenuTheme%, % disableAutoThemeConfigBySelectedThemeFn
34 | }
35 |
36 | addAutoThemeOpts() {
37 | Gui EditConfig:Add, GroupBox, xs y+10 w280 hWndAutoThemeGroupBox Section, Automatic Theme Options
38 | this.CONFIG_HANDLE_AUTO_THEME_GROUPBOX := AutoThemeGroupBox
39 | Gui EditConfig:Add, Text, xs+10 yp+20 w140 hWndDarkStartText, Activate dark theme at:
40 | this.CONFIG_HANDLE_DARK_START_TEXT := DarkStartText
41 |
42 | Gui EditConfig:Add, DDL, % "x+0 yp-2 w35 hWndDarkStartHr Choose" this.configThemeManager.CONFIG_VAL_DARK_START_HR + 0, % this.HR_OPTIONS
43 | this.CONFIG_HANDLE_DARK_START_HR := DarkStartHr
44 |
45 | Gui EditConfig:Add, DDL, % "x+5 yp w35 hWndDarkStartMin Choose" this.configThemeManager.CONFIG_VAL_DARK_START_MIN + 1, % this.MINUTE_OPTIONS
46 | this.CONFIG_HANDLE_DARK_START_MIN := DarkStartMin
47 |
48 | Gui EditConfig:Add, DDL, % "x+5 yp w40 hWndDarkStartPM Choose" this.configThemeManager.CONFIG_VAL_DARK_START_PM + 1, AM|PM
49 | this.CONFIG_HANDLE_DARK_START_PM := DarkStartPM
50 |
51 |
52 | Gui EditConfig:Add, Text, xs+10 y+8 w140 hWndDarkStopText, Deactivate dark theme at:
53 | this.CONFIG_HANDLE_DARK_STOP_TEXT := DarkStopText
54 |
55 | Gui EditConfig:Add, DDL, % "x+0 yp-2 w35 hWndDarkStopHr Choose" this.configThemeManager.CONFIG_VAL_DARK_STOP_HR + 0, % this.HR_OPTIONS
56 | this.CONFIG_HANDLE_DARK_STOP_HR := DarkStopHr
57 |
58 | Gui EditConfig:Add, DDL, % "x+5 yp w35 hWndDarkStopMin Choose" this.configThemeManager.CONFIG_VAL_DARK_STOP_MIN + 1, % this.MINUTE_OPTIONS
59 | this.CONFIG_HANDLE_DARK_STOP_MIN := DarkStopMin
60 |
61 | Gui EditConfig:Add, DDL, % "x+5 yp w40 hWndDarkStopPM Choose" this.configThemeManager.CONFIG_VAL_DARK_STOP_PM + 1, AM|PM
62 | this.CONFIG_HANDLE_DARK_STOP_PM := DarkStopPM
63 |
64 | if(this.configManager.getTheme() = "dark") {
65 | CtlColors.Attach(DarkStartHr, "43474A","CCCCCC")
66 | CtlColors.Attach(DarkStartMin, "43474A","CCCCCC")
67 | CtlColors.Attach(DarkStartPM, "43474A","CCCCCC")
68 | CtlColors.Attach(DarkStopHr, "43474A","CCCCCC")
69 | CtlColors.Attach(DarkStopMin, "43474A","CCCCCC")
70 | CtlColors.Attach(DarkStopPM, "43474A","CCCCCC")
71 | }
72 | }
73 |
74 | disableAutoThemeConfigBySelectedTheme() {
75 | selectedTheme := GetControlValue(this.CONFIG_HANDLE_THEME)
76 | if(selectedTheme != "auto") {
77 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_AUTO_THEME_GROUPBOX
78 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_START_TEXT
79 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_START_HR
80 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_START_MIN
81 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_START_PM
82 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_STOP_TEXT
83 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_STOP_HR
84 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_STOP_MIN
85 | GuiControl, EditConfig:Disable, % this.CONFIG_HANDLE_DARK_STOP_PM
86 | } else {
87 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_AUTO_THEME_GROUPBOX
88 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_START_TEXT
89 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_START_HR
90 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_START_MIN
91 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_START_PM
92 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_STOP_TEXT
93 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_STOP_HR
94 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_STOP_MIN
95 | GuiControl, EditConfig:Enable, % this.CONFIG_HANDLE_DARK_STOP_PM
96 | }
97 | }
98 |
99 | generateMinuteOptions() {
100 | t := "00|01|02|03|04|05|06|07|08|09"
101 | Loop, % loopIndex := 50
102 | t := t . "|" . (60 - loopIndex--)
103 | this.MINUTE_OPTIONS := t
104 | }
105 |
106 | saveConfigs() {
107 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_THEME, GetControlValue(this.CONFIG_HANDLE_THEME))
108 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_START_HR, GetControlValue(this.CONFIG_HANDLE_DARK_START_HR))
109 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_START_MIN, GetControlValue(this.CONFIG_HANDLE_DARK_START_MIN))
110 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_START_PM, this.convertPMValToBinary(GetControlValue(this.CONFIG_HANDLE_DARK_START_PM)))
111 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_STOP_HR, GetControlValue(this.CONFIG_HANDLE_DARK_STOP_HR))
112 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_STOP_MIN, GetControlValue(this.CONFIG_HANDLE_DARK_STOP_MIN))
113 | this.configManager.writeConfigToFile(this.configThemeManager.CONFIG_NAME_DARK_STOP_PM, this.convertPMValToBinary(GetControlValue(this.CONFIG_HANDLE_DARK_STOP_PM)))
114 | }
115 |
116 | convertPMValToBinary(pmVal) {
117 | return pmVal = "PM"
118 | }
119 | }
--------------------------------------------------------------------------------
/src/config/ConfigManager.ahk:
--------------------------------------------------------------------------------
1 | #Include %A_ScriptDir%\src\config\ConfigThemeManager.ahk
2 |
3 | ;Handles configuration file reading & writing
4 | class ConfigManager {
5 | static CONFIG_FILE_NAME := "config.ini"
6 | static CONFIG_SECTION := "Configuration Options"
7 | static CONFIG_NAME_HOTKEY := "SHOW_MENU_HOTKEY"
8 | static CONFIG_NAME_MAX_CLIPS_TO_STORE := "MAX_CLIPS_TO_STORE"
9 | static CONFIG_NAME_MAX_WIDTH := "MAX_WIDTH"
10 | static CONFIG_NAME_MAX_HEIGHT := "MAX_HEIGHT"
11 | static CONFIG_NAME_ALT_PASTE_APPS := "SHIFT_INS_PASTE_APPS"
12 | static CONFIG_NAME_CONV_SPEC_CHAR := "CONV_SPEC_CHAR"
13 |
14 | static CONFIG_VAL_HOTKEY
15 | static CONFIG_VAL_MAX_CLIPS_TO_STORE
16 | static CONFIG_VAL_MAX_WIDTH
17 | static CONFIG_VAL_MAX_HEIGHT
18 | static CONFIG_VAL_ALT_PASTE_APPS
19 | static CONFIG_VAL_CONV_SPEC_CHAR
20 |
21 | __New(clipManager) {
22 | this.clipManager := clipManager
23 | this.configThemeManager := new MenuClip.Config.ConfigThemeManager(this)
24 | this.readAllConfigOptionsFromFile()
25 | this.writeAllConfigOptionsToFile()
26 |
27 | this.configManagerGui := new MenuClip.Config.ConfigManagerGui(this)
28 | }
29 |
30 | readConfigFromFile(configKeyName, defaultValue) {
31 | IniRead, tmpReadInStorageVar, % this.CONFIG_FILE_NAME, % this.CONFIG_SECTION, %configKeyName%, %defaultValue%
32 | return tmpReadInStorageVar
33 | }
34 |
35 | readAllConfigOptionsFromFile() {
36 | this.CONFIG_VAL_HOTKEY := this.readConfigFromFile(this.CONFIG_NAME_HOTKEY, "^+v")
37 | this.CONFIG_VAL_MAX_CLIPS_TO_STORE := this.readConfigFromFile(this.CONFIG_NAME_MAX_CLIPS_TO_STORE, 100)
38 | this.CONFIG_VAL_MAX_WIDTH := this.readConfigFromFile(this.CONFIG_NAME_MAX_WIDTH, 350)
39 | this.CONFIG_VAL_MAX_HEIGHT := this.readConfigFromFile(this.CONFIG_NAME_MAX_HEIGHT, 12)
40 | this.CONFIG_VAL_ALT_PASTE_APPS := this.readConfigFromFile(this.CONFIG_NAME_ALT_PASTE_APPS, A_Space)
41 | this.CONFIG_VAL_CONV_SPEC_CHAR := this.readConfigFromFile(this.CONFIG_NAME_CONV_SPEC_CHAR, 1)
42 | this.configThemeManager.determineTheme()
43 | }
44 |
45 |
46 | writeConfigToFile(configKeyName, configValue) {
47 | IniWrite, %configValue%, % this.CONFIG_FILE_NAME, % this.CONFIG_SECTION, %configKeyName%
48 | }
49 |
50 | writeAllConfigOptionsToFile() {
51 | this.writeConfigToFile(this.CONFIG_NAME_HOTKEY, this.CONFIG_VAL_HOTKEY)
52 | this.writeConfigToFile(this.CONFIG_NAME_MAX_CLIPS_TO_STORE, this.CONFIG_VAL_MAX_CLIPS_TO_STORE)
53 | this.writeConfigToFile(this.CONFIG_NAME_MAX_WIDTH, this.CONFIG_VAL_MAX_WIDTH)
54 | this.writeConfigToFile(this.CONFIG_NAME_MAX_HEIGHT, this.CONFIG_VAL_MAX_HEIGHT)
55 | this.writeConfigToFile(this.CONFIG_NAME_ALT_PASTE_APPS, this.CONFIG_VAL_ALT_PASTE_APPS)
56 | this.writeConfigToFile(this.CONFIG_NAME_CONV_SPEC_CHAR, this.CONFIG_VAL_CONV_SPEC_CHAR)
57 | this.configThemeManager.writeThemeConfigs()
58 | }
59 |
60 | getShowMenuHotkey() {
61 | return this.CONFIG_VAL_HOTKEY
62 | }
63 |
64 | getMaxClipsToStore() {
65 | return this.CONFIG_VAL_MAX_CLIPS_TO_STORE
66 | }
67 |
68 | getMaxWidth() {
69 | return this.CONFIG_VAL_MAX_WIDTH
70 | }
71 |
72 | getMaxHeight() {
73 | return this.CONFIG_VAL_MAX_HEIGHT
74 | }
75 |
76 | getAltPasteApps() {
77 | return this.CONFIG_VAL_ALT_PASTE_APPS
78 | }
79 |
80 | getConvSpecChar() {
81 | return this.CONFIG_VAL_CONV_SPEC_CHAR
82 | }
83 |
84 | getTheme() {
85 | return this.configThemeManager.calculatedTheme
86 | }
87 |
88 | openEditConfigWindow() {
89 | this.configManagerGui.showGui()
90 | }
91 | }
--------------------------------------------------------------------------------
/src/config/ConfigManagerGui.ahk:
--------------------------------------------------------------------------------
1 | #Include %A_ScriptDir%\src\config\ConfigGuiGeneralOptions.ahk
2 | #Include %A_ScriptDir%\src\config\ConfigGuiThemeOptions.ahk
3 |
4 | ;Handles configuration file reading & writing
5 | class ConfigManagerGui {
6 | static configManager
7 | static themeStyle
8 | static configGuiGeneralOptions, configGuiThemeOptions
9 | static CONFIG_HANDLE_BUTTON_SAVE_AND_RELOAD
10 | __New(configManager) {
11 | this.configManager := configManager
12 | this.themeStyle := this.configManager.getTheme()
13 | this.configGuiGeneralOptions := new MenuClip.Config.ConfigGuiGeneralOptions(configManager)
14 | this.configGuiThemeOptions := new MenuClip.Config.ConfigGuiThemeOptions(configManager)
15 |
16 | Gui +hWndEditConfig
17 | Gui EditConfig:-MinimizeBox -MaximizeBox
18 | Gui EditConfig:Margin, 10, 10
19 |
20 | if(this.themeStyle = "dark") {
21 | Gui EditConfig:Color, 2B2B2B, 43474A
22 | Gui EditConfig:Font, cDDDDDD
23 | }
24 |
25 | Gui EditConfig:Add, Tab3, , General|Theme
26 | Gui EditConfig:Tab, General
27 | this.configGuiGeneralOptions.addAllOptions()
28 | Gui EditConfig:Tab, Theme
29 | this.configGuiThemeOptions.addAllThemeOptions()
30 | Gui EditConfig:Tab
31 | this.addSaveAndReloadButton()
32 | }
33 |
34 | addSaveAndReloadButton() {
35 | Gui EditConfig:Add, Button, x107 y+10 w110 h26 hWndSaveAndReload +Default, Save and Reload
36 | this.CONFIG_HANDLE_BUTTON_SAVE_AND_RELOAD := SaveAndReload
37 | saveConfigsAndReloadFn := ObjBindMethod(this, "saveConfigsAndReload")
38 | GuiControl +g, %SaveAndReload%, % saveConfigsAndReloadFn
39 |
40 | if(this.themeStyle = "dark") {
41 | NORMAL_STATE := [0, 0x43474A, , 0xDDDDDD, , , "White"]
42 | HOVER_STATE := [0, "Gray", , "White", , , "White"]
43 | ImageButton.create(SaveAndReload, NORMAL_STATE, HOVER_STATE)
44 | }
45 | }
46 |
47 | saveConfigsAndReload() {
48 | this.configGuiGeneralOptions.saveConfigs()
49 | this.configGuiThemeOptions.saveConfigs()
50 | Reload
51 | }
52 |
53 | showGui() {
54 | Gui EditConfig:Show, Autosize, Edit Configuration
55 | }
56 | }
--------------------------------------------------------------------------------
/src/config/ConfigThemeManager.ahk:
--------------------------------------------------------------------------------
1 | ;Handles configuration file reading & writing
2 | class ConfigThemeManager {
3 | static CONFIG_NAME_THEME := "THEME"
4 | static CONFIG_NAME_DARK_START_HR := "DARK_START_HR"
5 | static CONFIG_NAME_DARK_START_MIN := "DARK_START_MIN"
6 | static CONFIG_NAME_DARK_START_PM := "DARK_START_PM"
7 | static CONFIG_NAME_DARK_STOP_HR := "DARK_STOP_HR"
8 | static CONFIG_NAME_DARK_STOP_MIN := "DARK_STOP_MIN"
9 | static CONFIG_NAME_DARK_STOP_PM := "DARK_STOP_PM"
10 |
11 | static CONFIG_VAL_THEME, calculatedTheme
12 | static CONFIG_VAL_DARK_START_HR, CONFIG_VAL_DARK_START_MIN, CONFIG_VAL_DARK_START_PM
13 | static CONFIG_VAL_DARK_STOP_HR, CONFIG_VAL_DARK_STOP_MIN, CONFIG_VAL_DARK_STOP_PM
14 |
15 | __New(configManager) {
16 | this.configManager := configManager
17 | }
18 |
19 | determineTheme() {
20 | this.CONFIG_VAL_THEME := this.configManager.readConfigFromFile(this.CONFIG_NAME_THEME, "light")
21 | this.CONFIG_VAL_DARK_START_HR := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_START_HR, "05")
22 | this.CONFIG_VAL_DARK_START_MIN := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_START_MIN, "00")
23 | this.CONFIG_VAL_DARK_START_PM := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_START_PM, "1")
24 | this.CONFIG_VAL_DARK_STOP_HR := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_STOP_HR, "09")
25 | this.CONFIG_VAL_DARK_STOP_MIN := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_STOP_MIN, "00")
26 | this.CONFIG_VAL_DARK_STOP_PM := this.configManager.readConfigFromFile(this.CONFIG_NAME_DARK_STOP_PM, "0")
27 |
28 | if(this.CONFIG_VAL_THEME = "auto") {
29 | darkStartHr := this.CONFIG_VAL_DARK_START_HR + (this.CONFIG_VAL_DARK_START_PM ? 12 : 0)
30 | darkStopHr := this.CONFIG_VAL_DARK_STOP_HR + (this.CONFIG_VAL_DARK_STOP_PM ? 12 : 0)
31 |
32 | darkStartTime := this.convertTimeToFullStr(darkStartHr, this.CONFIG_VAL_DARK_START_MIN)
33 | darkStopTime := this.convertTimeToFullStr(darkStopHr, this.CONFIG_VAL_DARK_STOP_MIN)
34 |
35 | if(darkStopTime < darkStartTime) {
36 | EnvAdd, darkStopTime, 1, Days
37 | }
38 | if(A_Now >= darkStartTime && A_Now < darkStopTime) {
39 | this.calculatedTheme := "dark"
40 | reloadTime := this.getNextOccurrenceOfTimeAsFullStr(darkStopHr, this.CONFIG_VAL_DARK_STOP_MIN)
41 | EnvSub, reloadTime, %A_Now%, Seconds
42 | } else {
43 | this.calculatedTheme := "light"
44 | reloadTime := this.getNextOccurrenceOfTimeAsFullStr(darkStartHr, this.CONFIG_VAL_DARK_START_MIN)
45 | EnvSub, reloadTime, %A_Now%, Seconds
46 | }
47 | triggerGuiRecolorFn := ObjBindMethod(this, "triggerGuiRecolor")
48 | SetTimer, % triggerGuiRecolorFn, % reloadTime * -1000
49 | } else {
50 | this.calculatedTheme := this.CONFIG_VAL_THEME
51 | }
52 | }
53 |
54 | writeThemeConfigs() {
55 | this.configManager.writeConfigToFile(this.CONFIG_NAME_THEME, this.CONFIG_VAL_THEME)
56 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_START_HR, this.CONFIG_VAL_DARK_START_HR)
57 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_START_MIN, this.CONFIG_VAL_DARK_START_MIN)
58 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_START_PM, this.CONFIG_VAL_DARK_START_PM)
59 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_STOP_HR, this.CONFIG_VAL_DARK_STOP_HR)
60 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_STOP_MIN, this.CONFIG_VAL_DARK_STOP_MIN)
61 | this.configManager.writeConfigToFile(this.CONFIG_NAME_DARK_STOP_PM, this.CONFIG_VAL_DARK_STOP_PM)
62 | }
63 |
64 | ;parts were derived from https://autohotkey.com/board/topic/19960-got-problems-while-checking-the-system-time/://autohotkey.com/board/topic/19960-got-problems-while-checking-the-system-time/page-2?&
65 | ;not under MIT license
66 | ;takes in an hour (24-hour format) and a minute. Returns the next occurrence of that time today
67 | getNextOccurrenceOfTimeAsFullStr(hour, min) {
68 | next := this.convertTimeToFullStr(hour, min)
69 | if (next < A_Now) {
70 | EnvAdd, next, 1, Days
71 | }
72 | return next
73 | }
74 |
75 | ;converts an hour (24-hour format) and a minute into a full string with YYYYMMDDHH24MISS format
76 | convertTimeToFullStr(hour, min) {
77 | hourStr := (StrLen(hour) = 1 ? "0" : "") hour ;pads single digit hour
78 | minStr := (StrLen(min) = 1 ? "0" : "") min
79 | hhmm := hourStr . minStr
80 | return % A_YYYY A_MM A_DD hhmm "00"
81 | }
82 |
83 | triggerGuiRecolor() {
84 | recolorOpts := {"light":"dark", "dark":"light"}
85 | this.configManager.clipManager.menuGui.recolorGui(recolorOpts[this.calculatedTheme])
86 | this.configManager.clipManager.menuGui.initReloadOnMenuQuit()
87 | }
88 | }
--------------------------------------------------------------------------------
/src/controller/ClipManager.ahk:
--------------------------------------------------------------------------------
1 | ;Primary clipboard manager class. Should only be one instance for the duration of the script
2 | class ClipManager {
3 | static CLIP_TYPE_TEXT := 1
4 | static clipStore
5 | static MAX_CLIPS_TO_STORE
6 | static ALT_PASTE_APPS
7 | static lastSavedClipType
8 | __New() {
9 | this.configManager := new MenuClip.Config.ConfigManager(this)
10 | this.MAX_CLIPS_TO_STORE := this.configManager.getMaxClipsToStore()
11 | this.ALT_PASTE_APPS := this.configManager.getAltPasteApps()
12 | this.clipStore := new MenuClip.Model.ClipStore(this.configManager)
13 | this.postNewClipFn := ObjBindMethod(this, "postNewClip")
14 | this.saveClipFn := ObjBindMethod(this, "saveClip")
15 |
16 | this.menuGui := new MenuClip.View.MenuGui(this.configManager, this.clipStore, ObjBindMethod(this, "pasteClip"))
17 | }
18 |
19 | monitorClipboardChanges() {
20 | OnClipboardChange(this.postNewClipFn)
21 | }
22 |
23 | postNewClip(clipType) {
24 | this.lastSavedClipType := clipType
25 | saveClipFn := this.saveClipFn
26 | SetTimer, % saveClipFn, -250
27 | }
28 |
29 | saveClip() {
30 | clipType := this.lastSavedClipType
31 | if (clipType = this.CLIP_TYPE_TEXT) {
32 | ;avoid recording consecutive identical copies
33 | if(Clipboard = this.clipStore.getAtIndex(1)) {
34 | return
35 | } else if(this.clipStore.getSize() < this.MAX_CLIPS_TO_STORE) {
36 | this.clipStore.insertAtTop(Clipboard)
37 | this.menuGui.menuClipsViewHandler.insertItemAtTop(Clipboard)
38 | } else {
39 | this.clipStore.deleteAtIndex(this.MAX_CLIPS_TO_STORE)
40 | this.menuGui.menuClipsViewHandler.deleteItemAtIndex(this.MAX_CLIPS_TO_STORE)
41 | this.clipStore.insertAtTop(Clipboard)
42 | this.menuGui.menuClipsViewHandler.insertItemAtTop(Clipboard)
43 | }
44 | }
45 | }
46 |
47 | pasteClip(posClicked) {
48 | OnClipboardChange(this.postNewClipFn, 0)
49 | Clipboard := this.clipStore.getAtIndex(posClicked)
50 | WinGet, activeWin, ProcessName, A
51 | if(InStr(this.ALT_PASTE_APPS, activeWin)) {
52 | Send, +{Insert}
53 | } else {
54 | Send, ^v
55 | }
56 | this.clipStore.moveToTop(posClicked)
57 | this.menuGui.menuClipsViewHandler.moveToTop(posClicked)
58 | OnClipboardChange(this.postNewClipFn)
59 | }
60 |
61 | showContextMenu() {
62 | this.menuGui.showGui()
63 | }
64 |
65 | __Delete() {
66 | OnClipboardChange(this.saveClipFn, 0)
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/controller/Controller.ahk:
--------------------------------------------------------------------------------
1 | ;Container class for the controller classes that MenuClip uses
2 | class Controller {
3 | #Include %A_ScriptDir%\src\controller\TrayManager.ahk
4 | #Include %A_ScriptDir%\src\controller\ClipManager.ahk
5 | }
--------------------------------------------------------------------------------
/src/controller/TrayManager.ahk:
--------------------------------------------------------------------------------
1 | ;Handles changes to the menu
2 | class TrayManager {
3 | static configManager
4 | static clipManager
5 | static VERSION
6 | __New(version, clipManager) {
7 | this.VERSION := version
8 | this.clipManager := clipManager
9 | this.configManager := clipManager.configManager
10 | }
11 |
12 | configureTrayTooltip() {
13 | Menu, Tray, Tip, % "MenuClip " . this.VERSION
14 | }
15 |
16 | configureTrayOptions() {
17 | Menu, Tray, NoStandard
18 | showAboutMessageFn := ObjBindMethod(this, "showAboutMessage")
19 | Menu, Tray, Add, &About, % showAboutMessageFn
20 | Menu, Tray, Add
21 | editConfigFileFn := ObjBindMethod(this, "editConfigFile")
22 | Menu, Tray, Add, Edit &Configuration, % editConfigFileFn
23 | Menu, Tray, Default, Edit &Configuration
24 | reloadScriptFn := ObjBindMethod(this, "reloadScript")
25 | Menu, Tray, Add, &Reload This Script, % reloadScriptFn
26 | clearCacheAndReloadFn := ObjBindMethod(this, "clearCacheAndReload")
27 | Menu, Tray, Add, Clear Cache && Reload, % clearCacheAndReloadFn
28 | Menu, Tray, Add
29 | exitScriptFn := ObjBindMethod(this, "exitScript")
30 | Menu, Tray, Add, &Exit, % exitScriptFn
31 | }
32 |
33 | showAboutMessage() {
34 | MsgBox, 0, About MenuClip, % "MenuClip " . this.VERSION . " (c) takanuva15 `n`n" this.ABOUT_MENUCLIP
35 | }
36 |
37 | editConfigFile() {
38 | this.configManager.openEditConfigWindow()
39 | }
40 |
41 | reloadScript() {
42 | Reload
43 | }
44 |
45 | clearCacheAndReload() {
46 | this.clipManager.clipStore.cacheDirManager.clearCache()
47 | this.reloadScript()
48 | }
49 |
50 | exitScript() {
51 | ExitApp
52 | }
53 |
54 | static ABOUT_MENUCLIP := ""
55 | . "MenuClip is a free, open-source clipboard manager. "
56 | . "It watches your clipboard and stores new clips into memory, which you "
57 | . "can recall later by invoking the 'clip menu' through a hotkey.`n`n"
58 | . "See the README.md file on the MenuClip GitHub page (bit.ly/MenuClip) for more info "
59 | . "(and visit my profile at github.com/takanuva15)`n"
60 | }
--------------------------------------------------------------------------------
/src/model/CacheDirManager.ahk:
--------------------------------------------------------------------------------
1 | ;Manages the cache directory which holds file backups for what's on the clip menu
2 | class CacheDirManager {
3 | static configManager
4 | static cacheDir := "cache\"
5 | static cachedClipFileNames := []
6 | __New(configManager) {
7 | this.configManager := configManager
8 | if(!InStr(FileExist(this.cacheDir), "D")) {
9 | FileCreateDir, % this.cacheDir
10 | }
11 | this.verifyCache()
12 | }
13 |
14 | ;returns array of what was restored from cache
15 | restoreFromCache() {
16 | Loop, Files, % this.cacheDir . "*.txt"
17 | this.cachedClipFileNames.push(A_LoopFileName)
18 |
19 | tmp := []
20 | Loop, % index := this.cachedClipFileNames.maxIndex()
21 | {
22 | FileRead, clip, % this.cacheDir . this.cachedClipFileNames[index--]
23 | tmp.insertAt(1, clip)
24 | }
25 | return tmp
26 | }
27 |
28 | insertAtTopOfCache(clip) {
29 | Loop, % index := this.cachedClipFileNames.length()
30 | {
31 | fileName := this.cachedClipFileNames[index]
32 | RegExMatch(fileName, "^(?P\d+)\.", file)
33 | FileMove, % this.cacheDir . fileName, % this.cacheDir . fileNum + 1 . ".txt"
34 | this.cachedClipFileNames[fileNum + 1] := fileNum + 1 . ".txt"
35 | index--
36 | }
37 | this.cachedClipFileNames.removeAt(1)
38 | FileAppend, %clip%, % this.cacheDir . "1.txt"
39 | this.cachedClipFileNames.insertAt(1, "1.txt")
40 | }
41 |
42 | moveToTopOfCache(index) {
43 | tmp := this.cachedClipFileNames[index]
44 | FileMove, % this.cacheDir . tmp, % this.cacheDir . tmp . ".tmp"
45 | Loop, % loopIndex := index - 1
46 | {
47 | fileName := this.cachedClipFileNames[loopIndex--]
48 | RegExMatch(fileName, "^(?P\d+)\.", file)
49 | FileMove, % this.cacheDir . fileName, % this.cacheDir . fileNum + 1 . ".txt"
50 | this.cachedClipFileNames[fileNum + 1] := fileNum + 1 . ".txt"
51 | }
52 | FileMove, % this.cacheDir . tmp . ".tmp", % this.cacheDir . "1.txt"
53 | }
54 |
55 | deleteFromCache(index) {
56 | ;Works as of now because we only ever delete the last element in ClipManager. However, should be a loop available to rename files below this in the cache if we theoretically deleted from the middle of the menu.
57 | FileDelete, % this.cacheDir this.cachedClipFileNames[index]
58 | this.cachedClipFileNames.removeAt(index)
59 | }
60 |
61 | clearCache() {
62 | FileRemoveDir, % this.cacheDir, 1
63 | FileCreateDir, % this.cacheDir
64 | }
65 |
66 | verifyCache() {
67 | fileList := []
68 | Loop, Files, % this.cacheDir . "*"
69 | fileList.push(A_LoopFileName)
70 | fileList := this.sortArray(fileList, "N")
71 | numFiles := fileList.maxIndex()
72 | maxFileCount := this.configManager.getMaxClipsToStore()
73 |
74 | ;Delete excess clips above maximum allowed
75 | Loop, % numFiles - maxFileCount
76 | FileDelete, % this.cacheDir . fileList[A_Index + maxFileCount]
77 |
78 | ;Temp name remaining clips prior to rename
79 | Loop, % numFiles > maxFileCount ? maxFileCount : numFiles
80 | FileMove, % this.cacheDir . fileList[A_Index], % this.cacheDir . fileList[A_Index] . ".bak"
81 |
82 | ;Rename remaining clips
83 | Loop, % index := numFiles > maxFileCount ? maxFileCount : numFiles
84 | {
85 | ;Msgbox % "Comparing " fileList[index] " with " index . ".txt"
86 | FileMove, % this.cacheDir . fileList[index] . ".bak", % this.cacheDir . index . ".txt"
87 | index--
88 | }
89 |
90 | }
91 |
92 | ;https://autohotkey.com/board/topic/93570-sortarray/
93 | ;Not under MIT license
94 | sortArray(arr,options="") {
95 | if !IsObject(arr)
96 | return 0
97 | new := []
98 | if (options="Flip") {
99 | While (i := arr.MaxIndex()-A_Index+1)
100 | new.Insert(arr[i])
101 | return new
102 | }
103 | list := ""
104 | For each, item in arr
105 | list .= item "`n"
106 | list := Trim(list,"`n")
107 | Sort, list, %options%
108 | Loop, parse, list, `n, `r
109 | new.Insert(A_LoopField)
110 | return new
111 | }
112 | }
--------------------------------------------------------------------------------
/src/model/ClipStore.ahk:
--------------------------------------------------------------------------------
1 | #Include %A_ScriptDir%\src\model\CacheDirManager.ahk
2 |
3 | ;Manages the stored clips. (Basically an array manager)
4 | class ClipStore {
5 | static clips := []
6 | static configManager
7 | static cacheDirManager
8 | __New(configManager) {
9 | this.configManager := configManager
10 | this.cacheDirManager := new MenuClip.Model.CacheDirManager(configManager)
11 | this.clips := this.cacheDirManager.restoreFromCache()
12 | }
13 |
14 | getClips() {
15 | return this.clips
16 | }
17 |
18 | getAtIndex(index) {
19 | return this.clips[index]
20 | }
21 |
22 | insertAtTop(clip) {
23 | this.clips.insertAt(1, clip)
24 | this.cacheDirManager.insertAtTopOfCache(clip)
25 | }
26 |
27 | moveToTop(index) {
28 | tmp := this.clips[index]
29 | Loop, % loopIndex := index - 1
30 | this.clips[loopIndex + 1] := this.clips[loopIndex--]
31 | this.clips[1] := tmp
32 |
33 | this.cacheDirManager.moveToTopOfCache(index)
34 | }
35 |
36 | deleteAtIndex(index) {
37 | this.clips.removeAt(index)
38 | this.cacheDirManager.deleteFromCache(index)
39 | }
40 |
41 | getSize() {
42 | ;avoids returning blank if clips empty
43 | return this.clips.maxIndex() ? this.clips.maxIndex() : 0
44 | }
45 |
46 | printClips() {
47 | s := ""
48 | for index, element in this.clips {
49 | s := s . element . ", "
50 | }
51 |
52 | t := ""
53 | for index, element in this.cachedClipFileNames {
54 | t := t . element . ", "
55 | }
56 | MsgBox % s . "`n`n" . t
57 | }
58 | }
--------------------------------------------------------------------------------
/src/model/Model.ahk:
--------------------------------------------------------------------------------
1 | ;Container class for the model classes that MenuClip uses
2 | class Model {
3 | #Include %A_ScriptDir%\src\model\ClipStore.ahk
4 | }
--------------------------------------------------------------------------------
/src/test_scenarios.md:
--------------------------------------------------------------------------------
1 | # Test Scenarios
2 | ### This file lists the scenarios that should be tested prior to merging to develop
3 |
4 | All scenarios assume:
5 | - MAX_CLIPS_TO_STORE=3
6 | - ALT_PASTE_APPS=mintty.exe
7 |
8 | ### Functionality Tests
9 | 1. Max limit on stored clips obeyed
10 | Copy "four", "three", "two", "one", in that order. The menu should show "one", "two", "three", in that order.
11 |
12 | 1. Correct clip is pasted on selection
13 | From previous, paste "one". Now, paste "two".
14 |
15 | 1. Selected clip shifts to top of menu
16 | From previous, menu should show "two", "one", "three"
17 |
18 | 1. Pasting is correct in editors that use Shift+Insert for paste
19 | Open up git bash. Paste "three" into it.
20 |
21 | 1. No repeated copies
22 | Copy "five" multiple times. There should only be one entry of it at the top of the menu.
23 |
24 | #### Gui-related functionality
25 |
26 | 1. Open the menu. Use the arrow keys to select a paste option and press "enter" to paste it.
27 |
28 | 1. Type some letters into the search filter. Then use the arrow keys to move the highlighted selection around. Both should work interchangeably.
29 |
30 | 1. Filter the entries by a few letters. Click on an entry and paste it.
31 |
32 | 1. Filter the entries by a few letters. Use the arrow keys to select an entry and hit "enter" to paste.
33 |
34 | 1. Ensure the search box is cleared each time you open the gui
35 |
36 | ### Storage Tests
37 | 1. Clip order is preserved
38 | * Make sure cache is empty & restart the script. Copy "four", "three", "two", "one", in that order. The cache folder should show "1.txt", "2.txt", "3.txt", in that order, with file contents "one", "two", "three", respectively.
39 | * Paste "two" from the menu. The cache folder should show the same file names, but now the file contents should be "two", "one", "three", respectively.
40 |
41 | 1. Cache contents restored on script start
42 | Exit the script and then run it. The menu should show "two", "one", "three", in that order. Paste "one". Check that the cache shows "1.txt", "2.txt", "3.txt" with file contents matching "one", "two", "three", respectively.
43 |
44 | 1. mess up the order and naming of the cache files. Reload the script. Everything should be in the proper order without exceeding the max limit of the number-of-clips-to-store config option.
45 |
46 | ### Visual Tests
47 | 1. Try to open up the paste window with your mouse at the bottom-right of the screen. The window should not render off-screen.
48 |
49 | 1. Paste a clip. There should be no fade animation. Now click outside of the clips view window. The box should fade away.
50 |
51 | ### Config File
52 | 1. Delete the config file. Restart the script and the config file should appear with default values.
53 |
54 | 1. Change the configuration keys in `config.ini`. The property that each variable defines should adjust accordingly when the script is restarted.
55 |
56 | 1. Do the previous especially for the auto theme config. Test different times and different hours, and swap them around for weird situations
57 |
58 | ### Tray Menu Options
59 | 1. Change the configurations through the GUI that appears when selecting "Edit Configuration" from the System Tray icon's menu. Saving changes on the GUI should change it in the config.ini.
60 |
61 | 1. While the script is running, change the 1st text file in the cache and save. Then click "reload script" in the tray. The 1st entry in the clip menu should reflect the change made in the file.
62 |
63 | 1. With items in the clip menu, click the "clear cache & reload" option in the tray. Then open up the menu again and it should be empty. Check the cache dir to make sure it is also empty.
64 |
65 | ### Bugfix Tests
66 | 1. Open up Terminal in IntelliJ (download it if you don't have it). Try highlighting a word in command prompt; then open the menu. There should only be one entry with the word you highlighted.
67 |
68 | 1. Open up the menu and click outside of it. The menu should close and you should be able to click-drag random text.
69 |
70 | 1. Open up the menu and type in a random search term. Exit the gui using the Escape key. Reopen the menu. There should be no search term there
--------------------------------------------------------------------------------
/src/view/MenuClipsViewHandler.ahk:
--------------------------------------------------------------------------------
1 | ;Handles changes to the clips view
2 |
3 | class MenuClipsViewHandler {
4 | static menuGui
5 | __New(menuGui) {
6 | this.menuGui := menuGui
7 | }
8 |
9 | populateMenuFromArray(arr) {
10 | if(this.menuGui.configManager.getConvSpecChar()) {
11 | prettifiedArr := []
12 | for index, element in arr {
13 | prettifiedArr.push(this.prettifyClip(element))
14 | }
15 | } else {
16 | prettifiedArr := arr
17 | }
18 | PopulateLBFromArray(this.menuGui.HANDLE_CLIPS_VIEW, prettifiedArr)
19 | }
20 |
21 | insertItemAtTop(item) {
22 | if(this.menuGui.configManager.getConvSpecChar()) {
23 | item := this.prettifyClip(item)
24 | }
25 | LB_InsertItemAtIndex(this.menuGui.HANDLE_CLIPS_VIEW, item, 1)
26 | ;MC-45 bugfix needed for new clips being added
27 | this.menuGui.menuSearchHandler.filterClipsByStr("")
28 | }
29 |
30 | deleteItemAtIndex(index) {
31 | LB_DeleteItem(this.menuGui.HANDLE_CLIPS_VIEW, index)
32 | }
33 |
34 | moveToTop(index) {
35 | this.deleteItemAtIndex(index)
36 | this.insertItemAtTop(this.clipStore.getAtIndex(1))
37 | }
38 |
39 | pasteSelectedClip() {
40 | selectedClipIndex := GetControlValue(this.menuGui.HANDLE_CLIPS_VIEW)
41 | origClipIndex := this.menuGui.menuSearchHandler.getOrigClipFromFilteredClipByIndex(selectedClipIndex)
42 | this.menuGui.onItemClickFn.call(origClipIndex)
43 | }
44 |
45 | prettifyClip(clip) {
46 | filteredClip := StrReplace(clip, A_Tab, "[\t]")
47 | filteredClip := StrReplace(filteredClip, "`r`n", "[\n]")
48 | filteredClip := StrReplace(filteredClip, "`r", "[\n]")
49 | filteredClip := StrReplace(filteredClip, "`n", "[\n]")
50 | return filteredClip
51 | }
52 | }
--------------------------------------------------------------------------------
/src/view/MenuGui.ahk:
--------------------------------------------------------------------------------
1 | #Include %A_ScriptDir%\src\view\MenuWindowHandler.ahk
2 | #Include %A_ScriptDir%\src\view\MenuClipsViewHandler.ahk
3 | #Include %A_ScriptDir%\src\view\MenuSearchHandler.ahk
4 |
5 | ;Represents the menu gui
6 | class MenuGui {
7 | static clipStore
8 | static menuWindowHandler
9 | static menuClipsViewHandler
10 | static menuSearchHandler
11 | static HANDLE_CLIPS_VIEW
12 | static HANDLE_SEARCH_BOX
13 | static HANDLE_GUI
14 | __New(configManager, clipStore, onItemClickFn) {
15 | this.configManager := configManager
16 | this.clipStore := clipStore
17 | this.onItemClickFn := onItemClickFn
18 | this.menuWindowHandler := new MenuClip.View.MenuWindowHandler(this)
19 | this.menuClipsViewHandler := new MenuClip.View.MenuClipsViewHandler(this)
20 | this.menuSearchHandler := new MenuClip.View.MenuSearchHandler(this)
21 |
22 | Gui +hWndClipMenu
23 | Gui ClipMenu:-MinimizeBox -MaximizeBox -Caption +LastFound AlwaysOnTop
24 | this.HANDLE_GUI := WinExist()
25 | Gui ClipMenu:Margin, 5, 5
26 | Gui ClipMenu:Font, % (FontOptions := "s10")
27 |
28 | this.themeStyle := this.configManager.getTheme()
29 | if(this.themeStyle = "dark") {
30 | Gui ClipMenu:Color, 2B2B2B, 43474A
31 | Gui ClipMenu:Font, cDDDDDD
32 | }
33 |
34 | this.addClipsView()
35 | this.addSearchBox()
36 | this.addInvisibleOKButton()
37 | this.menuWindowHandler.updateGuiDims()
38 | this.menuSearchHandler.handleSearch() ;called once to prefill the filtered menu
39 | }
40 |
41 | addClipsView() {
42 | LBS_NOINTEGRALHEIGHT := 0x0100
43 | Gui ClipMenu:Add, ListBox, % "xm ym w" . this.configManager.getMaxWidth() . " r" . this.configManager.getMaxHeight() . " hWndClipsView AltSubmit +0x0100"
44 | this.HANDLE_CLIPS_VIEW := ClipsView
45 | LB_AdjustItemHeight(ClipsView, 5)
46 | }
47 |
48 | addSearchBox() {
49 | Gui ClipMenu:Add, Edit, % "xm y+5 w" . this.configManager.getMaxWidth() . " hWndSearchBox"
50 | this.HANDLE_SEARCH_BOX := SearchBox
51 | handleSearchFn := ObjBindMethod(this.menuSearchHandler, "handleSearch")
52 | GuiControl +g, %SearchBox%, % handleSearchFn
53 | }
54 |
55 | ;handles user pressing Enter on the menu
56 | addInvisibleOKButton() {
57 | Gui ClipMenu:Add, Button, h0 w0 hWndPasteSelected +Default
58 | this.HANDLE_BUTTON_PASTE := PasteSelected
59 | pasteOnEnterFn := ObjBindMethod(this, "pasteOnEnter")
60 | GuiControl +g, %PasteSelected%, % pasteOnEnterFn
61 | }
62 |
63 | pasteOnEnter() {
64 | this.menuWindowHandler.hideMenuGui(False)
65 | this.menuClipsViewHandler.pasteSelectedClip()
66 | this.menuWindowHandler.resetGuiState()
67 | }
68 |
69 | showGui() {
70 | this.menuWindowHandler.showGui()
71 | }
72 |
73 | recolorGui(themeStyle) {
74 | if(themeStyle = "light") {
75 | Gui ClipMenu:Color, Default, Default
76 | GuiControl, +cBlack, % this.HANDLE_CLIPS_VIEW
77 | GuiControl, +cBlack, % this.HANDLE_SEARCH_BOX
78 | } else {
79 | Gui ClipMenu:Color, 2B2B2B, 43474A
80 | GuiControl, +cDDDDDD, % this.HANDLE_CLIPS_VIEW
81 | GuiControl, +cDDDDDD, % this.HANDLE_SEARCH_BOX
82 | }
83 | }
84 |
85 | ;if gui is open when theme change occurs, theme will swap immediately
86 | ;once user closes gui, then we reload so that config gui reflects the
87 | ;theme change as well
88 | initReloadOnMenuQuit() {
89 | if(WinExist("ahk_id " this.HANDLE_GUI)) {
90 | this.menuWindowHandler.reloadOnHide := True
91 | } else {
92 | Reload
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/src/view/MenuSearchHandler.ahk:
--------------------------------------------------------------------------------
1 | ;Handles changes to the search box
2 | class MenuSearchHandler {
3 | static menuGui
4 | static filteredClips := []
5 | __New(menuGui) {
6 | this.menuGui := menuGui
7 | }
8 |
9 | handleSearch() {
10 | searchStr := GetControlValue(this.menuGui.HANDLE_SEARCH_BOX)
11 | this.filterClipsByStr(searchStr)
12 | GuiControl, , % this.menuGui.HANDLE_CLIPS_VIEW, |
13 | this.menuGui.menuClipsViewHandler.populateMenuFromArray(this.convertFilteredClipsToStrArr())
14 | GuiControl, Choose, % this.menuGui.HANDLE_CLIPS_VIEW, 1
15 | }
16 |
17 | filterClipsByStr(searchStr) {
18 | this.filteredClips := []
19 | for index, element in this.menuGui.clipStore.getClips() {
20 | if(InStr(element, searchStr)) {
21 | this.filteredClips.push({"origIndex": index, "clip": element})
22 | }
23 | }
24 | }
25 |
26 | convertFilteredClipsToStrArr() {
27 | filteredClipsTextOnly := []
28 | for index, element in this.filteredClips {
29 | filteredClipsTextOnly.push(element.clip)
30 | }
31 | return filteredClipsTextOnly
32 | }
33 |
34 | getOrigClipFromFilteredClipByIndex(filteredClipIndex) {
35 | return this.filteredClips[filteredClipIndex].origIndex
36 | }
37 | }
--------------------------------------------------------------------------------
/src/view/MenuWindowHandler.ahk:
--------------------------------------------------------------------------------
1 | ;Handles changes to the menu
2 | class MenuWindowHandler {
3 | static menuGui
4 | static totalScreenWidth, totalScreenHeight
5 | static guiWidth, guiHeight
6 | static reloadOnHide := False
7 | __New(menuGui) {
8 | this.menuGui := menuGui
9 | hideGuiOnOutsideClickFn := ObjBindMethod(this, "watchMouseClickAndHideGuiOnOutsideClick")
10 | Hotkey, ~LButton, % hideGuiOnOutsideClickFn
11 | Hotkey, ~LButton, Off
12 |
13 | CoordMode, Mouse, Screen
14 | SysGet, tmp, 78
15 | this.totalScreenWidth := tmp
16 | SysGet, tmp, 79
17 | this.totalScreenHeight := tmp
18 | }
19 |
20 | watchMouseClickAndHideGuiOnOutsideClick() {
21 | MouseGetPos, , , windowClicked, controlClicked
22 | if(controlClicked = "ListBox1") {
23 | Sleep, 100 ;allows time for Gui to show what was selected
24 | this.hideMenuGui(playFadeAnimation := False)
25 | this.menuGui.menuClipsViewHandler.pasteSelectedClip()
26 | this.resetGuiState()
27 | } else if(windowClicked = this.menuGui.HANDLE_GUI) {
28 | return
29 | } else {
30 | this.hideMenuGui()
31 | this.resetGuiState()
32 | }
33 | }
34 |
35 | showGui() {
36 | GuiControl, Focus, % this.menuGui.HANDLE_SEARCH_BOX
37 | GuiControl, Choose, % this.menuGui.HANDLE_CLIPS_VIEW, 1
38 | Hotkey, ~LButton, On
39 |
40 | MouseGetPos, mouseXPos, mouseYPos
41 | dispXPos := % mouseXPos + this.guiWidth > this.totalScreenWidth ? mouseXPos - this.guiWidth : mouseXPos
42 | dispYPos := % mouseYPos + this.guiHeight > this.totalScreenHeight ? mouseYPos - this.guiHeight : mouseYPos
43 | Gui ClipMenu:Show, AutoSize x%dispXPos% y%dispYPos%
44 | WinSet, Transparent, Off, % "ahk_id " this.menuGui.HANDLE_GUI
45 | this.handleKeyPresses()
46 | }
47 |
48 | handleKeyPresses() {
49 | Input, tmp, V, {Esc}{LWin}{RWin}{AppsKey}{LAlt}{RAlt}{Up}{Down}
50 | if(InStr(ErrorLevel, "EndKey:")) {
51 | if(InStr(ErrorLevel, "Up")) {
52 | GuiControl, Focus, % this.menuGui.HANDLE_CLIPS_VIEW
53 | Send, {Up}
54 | GuiControl, Focus, % this.menuGui.HANDLE_SEARCH_BOX
55 | } else if(InStr(ErrorLevel, "Down")) {
56 | GuiControl, Focus, % this.menuGui.HANDLE_CLIPS_VIEW
57 | Send, {Down}
58 | GuiControl, Focus, % this.menuGui.HANDLE_SEARCH_BOX
59 | } else {
60 | this.hideMenuGui()
61 | this.resetGuiState()
62 | return
63 | }
64 | this.handleKeyPresses()
65 | }
66 | }
67 |
68 | hideMenuGui(playFadeAnimation := True) {
69 | if(playFadeAnimation) {
70 | Loop, % loopIndex := 10
71 | {
72 | WinSet, Transparent, % loopIndex-- * 25, % "ahk_id " this.menuGui.HANDLE_GUI
73 | Sleep 25
74 | }
75 | }
76 | Gui ClipMenu:Hide
77 | if(this.reloadOnHide) {
78 | Reload
79 | }
80 | }
81 |
82 | resetGuiState() {
83 | GuiControl, , % this.menuGui.HANDLE_SEARCH_BOX
84 | Input
85 | Hotkey, ~LButton, Off
86 | Send, {Ctrl up} ;depresses Ctrl if you're using Ctrl in the showMenu shortcut
87 | }
88 |
89 | updateGuiDims() {
90 | Gui ClipMenu:Show, Hide ;Renders it once to give it dimensions for the handler to use
91 | GetGuiSize(this.menuGui.HANDLE_GUI, guiWidth, guiHeight)
92 | this.guiWidth := guiWidth
93 | this.guiHeight := guiHeight
94 | }
95 | }
--------------------------------------------------------------------------------
/src/view/View.ahk:
--------------------------------------------------------------------------------
1 | ;Container class for the view classes that MenuClip uses
2 | class View {
3 | #Include %A_ScriptDir%\src\view\MenuGui.ahk
4 | }
--------------------------------------------------------------------------------