Repository navigation
Expand file tree
/
Copy pathNotes.lua
More file actions
435 lines (372 loc) · 15.6 KB
/
Copy pathNotes.lua
File metadata and controls
435 lines (372 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
local fileList = {}
local currentIndex = 1
local notesPath = ""
local currentFilePath = ""
local initialized = false
function Initialize()
-- Initialize on first Update cycle
end
function Update()
if not initialized then
initialized = true
InitializeLogic()
return "Done"
end
end
function LogToFile(msg)
local path = SKIN:GetVariable('CURRENTPATH') .. 'notes_debug.txt'
local f = io.open(path, 'a')
if f then
f:write(os.date('%Y-%m-%d %H:%M:%S') .. ' - ' .. tostring(msg) .. '\n')
f:close()
end
end
-- Load configuration from notes-config.ini (if it exists)
-- Falls back silently to existing INI variable values if file not found
function LoadConfig()
local skinPath = SKIN:GetVariable('CURRENTPATH')
local configPath = skinPath .. 'notes-config.ini'
local f = io.open(configPath, 'r')
if not f then
LogToFile('Notes: notes-config.ini not found, using INI defaults')
return
end
local paths = {}
local subfolders = '1'
for line in f:lines() do
-- Skip comment lines (;) and blank lines
if not line:match('^%s*;') and not line:match('^%s*$') then
local key, val = line:match('^%s*([%w_]+)%s*=%s*(.-)%s*$')
if key and val then
-- Trim whitespace and surrounding quotes
val = val:gsub('^%s*["\']?(.-)["\']?%s*$', '%1')
if key == 'Subfolders' and val ~= '' then
subfolders = val
elseif val ~= '' then
-- Match Folder1..N, File1..N, Path1..N, NotesPath, etc.
if key:match('^Folder%d*$') or key:match('^File%d*$') or key:match('^Path%d*$') or key == 'NotesPath' then
for p in val:gmatch('[^;]+') do
p = p:gsub('^%s*["\']?(.-)["\']?%s*$', '%1')
if p ~= '' then
table.insert(paths, p)
end
end
end
end
end
end
end
f:close()
local combinedPaths = table.concat(paths, ';')
if combinedPaths ~= '' then
SKIN:Bang('!SetVariable', 'NotesPath', combinedPaths)
SKIN:Bang('!WriteKeyValue', 'Variables', 'NotesPath', combinedPaths)
end
SKIN:Bang('!SetVariable', 'Subfolders', subfolders)
SKIN:Bang('!WriteKeyValue', 'Variables', 'Subfolders', subfolders)
LogToFile('Notes: Config loaded from notes-config.ini. ' .. #paths .. ' path(s) configured.')
end
function InitializeLogic()
local status, err = pcall(function()
LoadConfig()
local rawPath = SKIN:GetVariable('NotesPath', '')
LogToFile("Initializing logic. Raw path: [" .. tostring(rawPath) .. "]")
notesPath = rawPath:gsub('"', '')
if notesPath ~= "" and notesPath:sub(-1) ~= "\\" then
notesPath = notesPath .. "\\"
end
LogToFile("Sanitized path: [" .. notesPath .. "]")
-- Trigger the RunCommand measure to list files
SKIN:Bang('!CommandMeasure', 'MeasureFileList', 'Run')
end)
if not status then
LogToFile("CRITICAL LUA ERROR: " .. tostring(err))
SafeBang('!SetOption', 'MeterNoteText', 'Text', 'LUA ERROR: ' .. tostring(err))
SafeBang('!UpdateMeter', 'MeterNoteText')
SafeBang('!Redraw')
end
end
-- Called from Rainmeter after MeasureFileList finishes
function ParseFileListFromMeasure()
local output = SKIN:GetMeasure('MeasureFileList'):GetStringValue()
LogToFile("Raw output from PS measure: [" .. tostring(output) .. "]")
fileList = {}
for line in output:gmatch("[^\r\n]+") do
if line and line:match('%S') then
-- PS FullName gives absolute paths
table.insert(fileList, line)
end
end
LogToFile("Parsed " .. #fileList .. " absolute paths from measure.")
if #fileList > 0 then
if currentIndex > #fileList then currentIndex = 1 end
UpdateRainmeter()
else
LogToFile("No files found after parsing.")
SafeBang('!SetOption', 'MeterTitle', 'Text', 'Notes Viewer (No Files)')
SafeBang('!SetOption', 'MeterTitle', 'ToolTipText', '')
SafeBang('!SetOption', 'MeterNoteText', 'Text', 'No notes found. Right-click -> Edit Config to add folders or files.')
SafeBang('!UpdateMeter', '*')
SafeBang('!Redraw')
end
end
local currentLines = {}
local scrollLine = 1
function UpdateRainmeter()
if #fileList == 0 then return end
currentFilePath = fileList[currentIndex]
LogToFile("Reading file: " .. currentFilePath)
currentLines = {}
local f = io.open(currentFilePath, "r")
if f then
local raw = f:read("*all")
f:close()
for line in (raw .. "\n"):gmatch("(.-)\r?\n") do
table.insert(currentLines, line)
end
if #currentLines == 0 then
table.insert(currentLines, "")
end
else
LogToFile("Failed to open file for reading.")
table.insert(currentLines, "Error: Could not read file [*#CRLF#]" .. currentFilePath .. "[*]")
end
scrollLine = 1
RenderCurrentNote()
end
function RenderCurrentNote()
if #fileList == 0 then return end
local filename = currentFilePath:match("([^\\]+)$") or currentFilePath
local titleText = filename .. " (" .. currentIndex .. " / " .. #fileList .. ")"
titleText = titleText:gsub('%[', '[*'):gsub('%]', '*]')
local endLine = math.min(#currentLines, scrollLine + 120)
local slice = {}
for i = scrollLine, endLine do
table.insert(slice, currentLines[i])
end
local content = table.concat(slice, '#CRLF#')
content = content:gsub('%[', '[*'):gsub('%]', '*]')
local tooltipPath = currentFilePath:gsub('%[', '[*'):gsub('%]', '*]')
SafeBang('!SetOption', 'MeterTitle', 'ToolTipText', tooltipPath)
SafeBang('!SetOption', 'MeterTitle', 'Text', titleText)
SafeBang('!SetOption', 'MeterNoteText', 'Text', content)
SafeBang('!SetVariable', 'CurrentFile', currentFilePath)
SafeBang('!UpdateMeter', 'MeterTitle')
SafeBang('!UpdateMeter', 'MeterNoteText')
SafeBang('!Redraw')
end
function ScrollNote(delta)
local d = tonumber(delta) or 0
if #currentLines <= 1 or d == 0 then return end
local newScroll = scrollLine + d
if newScroll < 1 then newScroll = 1 end
if newScroll > #currentLines then newScroll = #currentLines end
if newScroll ~= scrollLine then
scrollLine = newScroll
RenderCurrentNote()
end
end
local dropdownOpen = false
local dropdownScroll = 1
local MAX_DROPDOWN_ITEMS = 10
-- 5-second hover timer for notepad tooltip
local noteHoverActive = false
local noteHoverToken = 0
function StartNoteHover()
noteHoverActive = true
noteHoverToken = noteHoverToken + 1
end
function CheckNoteHover()
if noteHoverActive and not dropdownOpen then
SafeBang('!SetOption', 'MeterNoteText', 'ToolTipText', 'Double-click to open in Notepad')
SafeBang('!UpdateMeter', 'MeterNoteText')
end
end
function CancelNoteHover()
noteHoverActive = false
noteHoverToken = noteHoverToken + 1
SafeBang('!SetOption', 'MeterNoteText', 'ToolTipText', '')
SafeBang('!UpdateMeter', 'MeterNoteText')
end
function ToggleDropdown()
if dropdownOpen then
CloseDropdown()
else
OpenDropdown()
end
end
function OpenDropdown()
if #fileList == 0 then return end
dropdownOpen = true
CancelNoteHover()
if currentIndex > dropdownScroll + MAX_DROPDOWN_ITEMS - 1 or currentIndex < dropdownScroll then
dropdownScroll = math.max(1, math.min(currentIndex - 2, #fileList - MAX_DROPDOWN_ITEMS + 1))
end
SafeBang('!SetOption', 'MeterDropdownArrow', 'Shape', 'Path ChevronUp | Stroke Color #AccentColor# | StrokeWidth (1.8 * #Scale#) | StrokeLineJoin Round | StrokeLineCap Round')
SafeBang('!SetOption', 'MeterDropdownArrow', 'MouseLeaveAction', '[!SetOption MeterDropdownArrow Shape "Path ChevronUp | Stroke Color #AccentColor# | StrokeWidth (1.8 * #Scale#) | StrokeLineJoin Round | StrokeLineCap Round"][!SetOption MeterDropdownArrow Shape2 "Rectangle (-6 * #Scale#),(-6 * #Scale#),(22 * #Scale#),(22 * #Scale#),2 | Fill Color 0,0,0,1 | StrokeWidth 0"][!UpdateMeter MeterDropdownArrow][!Redraw]')
SafeBang('!UpdateMeter', 'MeterDropdownArrow')
RenderDropdown()
end
function CloseDropdown()
if not dropdownOpen then return end
dropdownOpen = false
SafeBang('!SetOption', 'MeterDropdownArrow', 'Shape', 'Path ChevronDown | Stroke Color #DimColor# | StrokeWidth (1.8 * #Scale#) | StrokeLineJoin Round | StrokeLineCap Round')
SafeBang('!SetOption', 'MeterDropdownArrow', 'MouseLeaveAction', '[!SetOption MeterDropdownArrow Shape "Path ChevronDown | Stroke Color #DimColor# | StrokeWidth (1.8 * #Scale#) | StrokeLineJoin Round | StrokeLineCap Round"][!SetOption MeterDropdownArrow Shape2 "Rectangle (-6 * #Scale#),(-6 * #Scale#),(22 * #Scale#),(22 * #Scale#),2 | Fill Color 0,0,0,1 | StrokeWidth 0"][!UpdateMeter MeterDropdownArrow][!Redraw]')
SafeBang('!UpdateMeter', 'MeterDropdownArrow')
SafeBang('!HideMeterGroup', 'DropdownGroup')
SafeBang('!Redraw')
end
function RenderDropdown()
if not dropdownOpen or #fileList == 0 then return end
local scale = tonumber(SKIN:GetVariable('Scale', '1.0')) or 1.0
local pad = tonumber(SKIN:GetVariable('Padding', '20')) or (20 * scale)
local width = tonumber(SKIN:GetVariable('Width', '600')) or (600 * scale)
local visibleCount = math.min(#fileList, MAX_DROPDOWN_ITEMS)
local itemH = 25 * scale
local bgPadX = pad + (5 * scale)
local bgPadY = pad + (42 * scale)
local bgW = width - (bgPadX * 2)
local bgH = (14 * scale) + (visibleCount * itemH)
local shapeStr = string.format('Rectangle %.1f,%.1f,%.1f,%.1f,%.1f | Fill Color 18,18,22,250 | Stroke Color 255,255,255,40 | StrokeWidth 1', bgPadX, bgPadY, bgW, bgH, 4 * scale)
SafeBang('!SetOption', 'MeterDropdownBG', 'Shape', shapeStr)
for i = 1, MAX_DROPDOWN_ITEMS do
local fileIdx = dropdownScroll + i - 1
local meterName = 'MeterDropDownItem' .. i
if fileIdx <= #fileList and i <= visibleCount then
local path = fileList[fileIdx]
local fname = path:match("([^\\]+)$") or path
local isCurrent = (fileIdx == currentIndex)
local prefix = isCurrent and " > " or " "
local itemText = prefix .. fname
itemText = itemText:gsub('%[', '[*'):gsub('%]', '*]')
SafeBang('!SetOption', meterName, 'Text', itemText)
if isCurrent then
SafeBang('!SetOption', meterName, 'FontColor', '255,255,255')
SafeBang('!SetOption', meterName, 'StringStyle', 'Bold')
else
SafeBang('!SetOption', meterName, 'FontColor', '255,255,255,180')
SafeBang('!SetOption', meterName, 'StringStyle', 'Normal')
end
SafeBang('!ShowMeter', meterName)
else
SafeBang('!HideMeter', meterName)
end
end
SafeBang('!ShowMeter', 'MeterDropdownOverlay')
SafeBang('!ShowMeter', 'MeterDropdownBG')
SafeBang('!UpdateMeterGroup', 'DropdownGroup')
SafeBang('!Redraw')
end
function SelectDropdownItem(slot)
local idx = dropdownScroll + (tonumber(slot) or 1) - 1
if idx >= 1 and idx <= #fileList then
currentIndex = idx
CloseDropdown()
UpdateRainmeter()
end
end
function ScrollDropdown(delta)
if not dropdownOpen or #fileList <= MAX_DROPDOWN_ITEMS then return end
local maxScroll = #fileList - MAX_DROPDOWN_ITEMS + 1
local newScroll = dropdownScroll + (tonumber(delta) or 0)
if newScroll < 1 then newScroll = 1 end
if newScroll > maxScroll then newScroll = maxScroll end
if newScroll ~= dropdownScroll then
dropdownScroll = newScroll
RenderDropdown()
end
end
function SafeBang(bangName, ...)
local params = {...}
local cmd = bangName
for _, p in ipairs(params) do
local s = tostring(p):gsub('"', '""')
cmd = cmd .. ' "' .. s .. '"'
end
SKIN:Bang(cmd)
end
function Next()
if #fileList == 0 then return end
CloseDropdown()
currentIndex = currentIndex + 1
if currentIndex > #fileList then currentIndex = 1 end
UpdateRainmeter()
end
function Previous()
if #fileList == 0 then return end
CloseDropdown()
currentIndex = currentIndex - 1
if currentIndex < 1 then currentIndex = #fileList end
UpdateRainmeter()
end
function OpenInNotepad()
if currentFilePath ~= "" then
LogToFile("Opening in notepad: " .. currentFilePath)
SKIN:Bang('["' .. currentFilePath .. '"]')
end
end
-- ==========================================
-- RESIZING / SCALING
-- ==========================================
local isDragging = false
local dragStartScale = 1.0
function StartResize()
CloseDropdown()
isDragging = true
dragStartScale = tonumber(SKIN:GetVariable('Scale', '1.0')) or 1.0
LogToFile(string.format("Notes: StartResize at scale=%.2f", dragStartScale))
end
function EndResize(releaseX, releaseY)
if not isDragging then return end
isDragging = false
local rx = tonumber(releaseX) or 0
local ry = tonumber(releaseY) or 0
local baseW = tonumber(SKIN:GetVariable('BaseWidth', '600')) or 600
local baseH = tonumber(SKIN:GetVariable('BaseHeight', '500')) or 500
local scaleByX = rx / baseW
local scaleByY = ry / baseH
local newScale
if math.abs(rx) < 20 and math.abs(ry) < 20 then
-- tiny drag = accidental, ignore
return
elseif math.abs(rx) > math.abs(ry) then
newScale = scaleByX
else
newScale = scaleByY
end
if newScale < 0.30 then newScale = 0.30 end
if newScale > 3.00 then newScale = 3.00 end
newScale = math.floor(newScale * 100 + 0.5) / 100
LogToFile(string.format("Notes: EndResize release=(%d,%d) newScale=%.2f", rx, ry, newScale))
SKIN:Bang('!WriteKeyValue', 'Variables', 'Scale', string.format('%.2f', newScale))
SKIN:Bang('!SetVariable', 'Scale', string.format('%.2f', newScale))
SKIN:Bang('!Refresh')
end
function ResetScale()
isDragging = false
LogToFile("Notes: ResetScale")
SKIN:Bang('!WriteKeyValue', 'Variables', 'Scale', '1.00')
SKIN:Bang('!SetVariable', 'Scale', '1.00')
SKIN:Bang('!Refresh')
end
function ScaleStep(delta)
local scale = tonumber(SKIN:GetVariable('Scale', '1.00')) or 1.00
scale = scale + (tonumber(delta) or 0)
if scale < 0.30 then scale = 0.30 end
if scale > 3.00 then scale = 3.00 end
scale = math.floor(scale * 100 + 0.5) / 100
LogToFile(string.format("Notes: ScaleStep -> %.2f", scale))
SKIN:Bang('!WriteKeyValue', 'Variables', 'Scale', string.format('%.2f', scale))
SKIN:Bang('!SetVariable', 'Scale', string.format('%.2f', scale))
SKIN:Bang('!Refresh')
end
function SetScale(value)
local scale = tonumber(value) or 1.00
if scale < 0.30 then scale = 0.30 end
if scale > 3.00 then scale = 3.00 end
scale = math.floor(scale * 100 + 0.5) / 100
LogToFile(string.format("Notes: SetScale -> %.2f", scale))
SKIN:Bang('!WriteKeyValue', 'Variables', 'Scale', string.format('%.2f', scale))
SKIN:Bang('!SetVariable', 'Scale', string.format('%.2f', scale))
SKIN:Bang('!Refresh')
end