-
Notifications
You must be signed in to change notification settings - Fork 1
/
NASA_APOD.cs
1369 lines (1178 loc) · 54.8 KB
/
NASA_APOD.cs
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
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using Microsoft.Win32;
using Microsoft.Win32.TaskScheduler;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace NASA_APOD
{
public partial class NASA_APOD : Form
{
//Main photo of the day object
public APOD Apod;
//Paths
public string PathToSave; //will hold path from folder dialog
public string ImagePath = @".\temp.jpg"; //default file to save
//GUI items
private bool FormHidden = false;
private Color AvgColor = SystemColors.ActiveCaption;
//setup ini file to store usage statistics
readonly IniFile IniFile = new();
//Wallpapering
[DllImport("kernel32.dll")]
static extern uint GetLastError();
[DllImport("user32.dll")]
static extern int SystemParametersInfo(int Action, int nParam, string sParam, int WinIni);
const int SPI_SETDESKWALLPAPER = 0x14;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDWININICHANGE = 0x02;
//Logging switch - disabled on startup, requires cmd line param to be enabled
readonly bool Logging = false;
//Grab archive
static readonly DateTime DateMin = DateTime.Parse("1995-06-16"); //first day in APOD archive
static readonly int DaysSpan = 1 + (int)(DateTime.Today - DateMin).TotalDays; //number of days from today to minimum date
int DaysProg = 0; //download progress
int DaysErrors = 0; //number of errors or non-images
//---- Default constructor - create program window and set everything up ----
public NASA_APOD()
{
//Enable logging if required - just pass anything as pgm parameter
//if (Environment.GetCommandLineArgs().Length > 1)
Logging = true;
//Disable certificate checking - dirty and temporary
//ServicePointManager.ServerCertificateValidationCallback += (a, b, c, d) => true;
Log("\n--- PROGRAM START -----------------------------------------------");
Log(MethodBase.GetCurrentMethod().Name);
Log("Initializing...");
InitializeComponent();
Log("DONE!");
//Read anything from INI file to check if it was already created
//If not, write initial zero number of runs
Log("Ini file first read");
string lastDate = IniFile.Read("lastDate");
if (lastDate == string.Empty) //write default values to INI file
{
Log("INI file empty, fill in with default values");
IniFile.Write("lastDate", DateTime.Today.ToString());
IniFile.Write("autoRefresh", checkAutoRefresh.Checked.ToString());
IniFile.Write("saveToDisk", checkSaveToDisk.Checked.ToString());
IniFile.Write("pathToSave", string.Empty);
IniFile.Write("useCustomKey", checkEnableHistory.Checked.ToString());
IniFile.Write("customKey", string.Empty);
IniFile.Write("enableHistory", checkEnableHistory.Checked.ToString());
}
//GUI items - START -------------------------------------------------------
Log("setting up GUI items...");
pictureBox.ErrorImage = Properties.Resources.NASA.ToBitmap();
pictureBox.InitialImage = Properties.Resources.NASA.ToBitmap();
if (!Logging) //remove debugging tab if not desired by pgm parameter
tabControl.TabPages.Remove(tabDebug);
//Add actual link to link label
LinkLabel.Link lnk = new() { LinkData = "https://api.nasa.gov/#signUp" };
linkHowToKey.Links.Add(lnk);
//Clear some labels
labelImageDesc.Text = string.Empty;
textDate.Text = string.Empty;
//Add menu to tray icon
trayIcon.ContextMenu = new(new MenuItem[]
{
new("Previous", ButtonPrev_Click) { Name = "menuPrev", OwnerDraw = true },
new("Next", ButtonNext_Click) { Name = "menuNext", OwnerDraw = true },
new("Today", OnMenuToday) { Name = "menuToday", OwnerDraw = true },
new("-") { OwnerDraw = false },
new("Exit", OnMenuExit) { Name = "menuExit", DefaultItem = true, OwnerDraw = true }
});
//Register custom menu measure&draw routines
foreach (MenuItem mi in trayIcon.ContextMenu.MenuItems)
{
mi.MeasureItem += MenuItemMeasure;
mi.DrawItem += MenuItemDraw;
}
//Setup icons for window and tray icon
trayIcon.Icon = Properties.Resources.NASA;
Icon = Properties.Resources.NASA;
//Now setup settings tab according to values stored in INI file
PathToSave = IniFile.Read("pathToSave");
textPath.Text = PathToSave;
if (IniFile.Read("saveToDIsk") == "True")
checkSaveToDisk.Checked = true;
else
checkSaveToDisk.Checked = false;
if (IniFile.Read("autoRefresh") == "True")
checkAutoRefresh.Checked = true;
else
checkAutoRefresh.Checked = false;
if (IniFile.Read("useCustomKey") == "True")
{
checkCustomKey.Checked = true;
textCustomKey.Enabled = true;
}
else
{
checkCustomKey.Checked = false;
textCustomKey.Enabled = false;
}
textCustomKey.Text = IniFile.Read("customKey");
//Setup history tab columns
listHistory.Columns[0].Text = "Date";
listHistory.Columns[1].Text = "Title";
//Remove history tab by default, then add if desired
tabControl.TabPages.Remove(tabHistory);
if (IniFile.Read("enableHistory") == "True")
checkEnableHistory.Checked = true;
else
checkEnableHistory.Checked = false;
//Now for run at startup birdie
checkRunAtStartup.Checked = new TaskService().RootFolder.GetTasks(new("NASA_APOD")).Count != 0 &&
new TaskService().RootFolder.GetTasks(new("NASA_APOD"))[new Regex("NASA_APOD").ToString()].Enabled;
Log("DONE!");
//GUI items - END -------------------------------------------------------
//Subscribe for events
AppDomain.CurrentDomain.ProcessExit += OnAppExit;
pictureBox.LoadProgressChanged += PictureBox_LoadProgressChanged; //image download progress bar
pictureBox.LoadCompleted += PictureBox_LoadCompleted; //image downloaded - rest of the logis is there
//Create 'photo of the day' object and set API date to today for starters
//At this point we should have custom key read from settingss. If not, default one will be used
Log("Create main apod object");
Apod = new();
Apod.SetApiKey(textCustomKey.Text);
Log("setting date - STARTUP...");
//If auto-refresh option is set, always use today's date
if (checkAutoRefresh.Checked)
SetApodDate(Apod, DateTime.Today);
else
SetApodDate(Apod, DateTime.Parse(IniFile.Read("lastDate")));
Log($"STARTUP DATE = {Apod.ApiDate}");
//Get the image on startup
Log("Getting the image on startup...");
GetNasaApod();
}
/// <summary>
/// Log string value to "apod.log" file
/// </summary>
/// <param name="logMessage">String value to be saved in log file</param>
private void Log(string logMessage)
{
if (Logging)
try
{
using TextWriter tw = new StreamWriter("apod.log", true);
tw.WriteLine($"{DateTime.Now} - {logMessage}");
}
catch (Exception ex)
{
Trace.Write(ex.ToString());
}
}
/// <summary>
/// Logging to tab - fill debug tab with raw API output json keys
/// </summary>
/// <param name="apod">APOD object</param>
private void DebugTab(APOD apod)
{
if (Logging)
{
//nothing fancy, use hard-coded list items to display API call response
listDebug.Items[0].SubItems[1].Text = apod.Copyright;
listDebug.Items[1].SubItems[1].Text = apod.Date;
listDebug.Items[2].SubItems[1].Text = apod.Explanation;
listDebug.Items[3].SubItems[1].Text = apod.HdUrl;
listDebug.Items[4].SubItems[1].Text = apod.MediaType;
listDebug.Items[5].SubItems[1].Text = apod.ServiceVersion;
listDebug.Items[6].SubItems[1].Text = apod.Title;
listDebug.Items[7].SubItems[1].Text = apod.Url;
listDebug.Items[8].SubItems[1].Text = string.Empty;
listDebug.Columns[0].Width = 75;
listDebug.Columns[1].Width = 1350;
}
}
/// <summary>
/// Fill history tab list with dates and headers
/// </summary>
private void FillHistory()
{
Log(MethodBase.GetCurrentMethod().Name);
//Local APOD to get img title list
using APOD a = new();
a.SetApiKey(textCustomKey.Text);
//Clear history list
listHistory.Items.Clear();
statusBar.Text = "Getting history items... (0/0)";
Application.DoEvents();
//Some history setup
byte maxDays = 8; //how many history items, hardcoded
byte cnt = 0; //items loaded
int daysback = 0; //start going back in time today
//Now go back in time and fetch history items
try
{
a.SetApiDate(Apod.ApiDate.AddDays(daysback)); //go back further back in time
while (cnt < maxDays && Apod.ApiDate.AddDays(daysback) >= DateTime.Parse("1995-06-16"))
{
listHistory.Items.Add(a.ApiDate.ToShortDateString()); //history date
listHistory.Items[cnt].SubItems.Add(a.Title); //history img name
cnt++; //item loaded!
progressBar.Value = 100 * cnt / maxDays; //update progress bar and status text
statusBar.Text = $"Getting history items... ({cnt}/{maxDays})";
Application.DoEvents();
Log($"History item added {a.Date}");
//Time travel
daysback--;
a.SetApiDate(Apod.ApiDate.AddDays(daysback));
}
listHistory.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
progressBar.Value = 100;
statusBar.Text = string.Empty;
}
catch (Exception e)
{
statusBar.Text = e.Message;
}
}
/// <summary>
/// Set current date for APOD object
/// </summary>
/// <param name="apod">APOD object</param>
/// <param name="apodDate">Desired date</param>
private void SetApodDate(APOD apod, DateTime apodDate)
{
Log(MethodBase.GetCurrentMethod().Name);
Log($"trying this date: {apodDate}");
//Clear date related GUI fields
textDate.ForeColor = SystemColors.GrayText;
textDate.Text = "Wait...";
Log("date field \"wait\" and grey");
//Try to call apod with desired date
try
{
Log("calling apod.setAPIDate()...");
apod.SetApiDate(apodDate);
//Save last image date to INI file
IniFile.Write("lastDate", apod.ApiDate.ToString());
Log($"Call succesful! apod.date = {apod.ApiDate}");
DebugTab(apod);
//Fill history items
if (checkEnableHistory.Checked)
FillHistory();
else
listHistory.Items.Clear();
}
catch (Exception e)
{
Log($"API SET DATE FAILED! requested date = {apodDate}");
Log(e.Message);
statusBar.Text = e.Message;
textDate.ForeColor = SystemColors.ControlText;
textDate.Text = "(none)";
}
finally
{
//Always try to setup prev/next buttons with previous and next dates ←→►◄
buttonPrev.Text = $"<< {apod.ApiDate.AddDays(-1).ToShortDateString()}";
buttonNext.Text = $"{apod.ApiDate.AddDays(1).ToShortDateString()} >>";
}
}
//Tray icon menu "today" event handler
private void OnMenuToday(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
//Get today's image
Apod.SetApiDate(DateTime.Today);
GetNasaApod();
}
//Tray icon menu "exit" event handler
private void OnMenuExit(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
Application.Exit();
}
//App exit event, just for logging
private void OnAppExit(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
Log("--- THE END! ------------------------------------------------");
}
/// <summary>
/// Get the picture and possibly save it to disk (if required in GUI)
/// </summary>
private void GetNasaApod()
{
Log(MethodBase.GetCurrentMethod().Name);
//Setup GUI items for download time
tabImage.Focus();
Log("setting some gui items...");
statusBar.Text = "Getting NASA picture of the day...";
trayIcon.Text = statusBar.Text;
textURL.Text = string.Empty;
buttonPrev.Enabled = false;
buttonToday.Enabled = false;
buttonNext.Enabled = false;
buttonRefresh.Enabled = false;
labelImageDesc.Text = string.Empty;
textBoxImgDesc.Text = string.Empty;
buttonPickDate.Enabled = false;
buttonCopyImage.Enabled = false;
buttonCopyLink.Enabled = false;
foreach (MenuItem mi in trayIcon.ContextMenu.MenuItems)
mi.Enabled = false;
Log("done!");
//Download image to picture box
Log("trying to download the image...");
//At this point apod object should be already initialized (ie. date was set)
//APOD can be either an image or video
//If it's an image - use picture box method to download picture in async...
if (Apod.IsImage)
{
//Download the image directly to image box
//"Image" property will allow to save it later
//(try normal-res picture by default)
//(hd images are large and use too much memory)
Log($"API says that image is there: {Apod.HdUrl}");
//Since this is async action, rest of the logic will be called whenever download is completed
labelImageDesc.Text = Apod.Title;
textBoxImgDesc.Text = "Loading...";
pictureBox.Visible = true;
web.Visible = false;
web.DocumentText = string.Empty;
pictureBox.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox.Image = Properties.Resources.NASA.ToBitmap();
//There's a chance that if 'save to disk' is enabled, the image was downloaded before
//In that case create full local path and try to load image from disk
Apod.IsDownloading = true;
if (checkSaveToDisk.Checked)
{
ImagePath = CreateFullPath(PathToSave, Apod);
if (File.Exists(ImagePath))
{
pictureBox.LoadAsync(ImagePath);
Log("async download started - from cache");
}
else
{
pictureBox.LoadAsync(Apod.HdUrl);
Log("async download started");
}
}
else
{
pictureBox.LoadAsync(Apod.HdUrl);
Log("async download started");
}
}
//...otherwise try to play video link
else
{
//Not a picture today, clear the image box with nasa icon and try to play video link
Log("not a picture section - either a video link or null");
pictureBox.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox.Image = Properties.Resources.NASA.ToBitmap();
pictureBox.Visible = true;
web.Visible = false;
//web.DocumentText = string.Empty;
//Now try to pick up video link
//Video link usually appears in 'url', but it's worth checking 'hdurl' as well
//So far only youtube and vimeo links were spotted, so let's tray that
if (Apod.VideoType != APOD.NasaVideoType.NONE)
{
//Switch to web player box, hide picture
web.Visible = true;
pictureBox.Visible = false;
//Create document to be displayed by web browser control
string DocumentText =
$"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\"/><iframe width={web.Width} height={web.Height} style=\"overflow: hidden; overflow - x:hidden; overflow - y:hidden; height: 100 %; width: 100 %; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px\" src=\"{{0}}\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe>";
//Try to inject video link into web document
string vsrc = string.Empty;
switch (Apod.VideoType)
{
case APOD.NasaVideoType.YOUTUBE:
vsrc = (Apod.Url != string.Empty) ? Apod.Url.Substring(Apod.Url.IndexOf(APOD.VID_LINK_YT)) : Apod.HdUrl.Substring(Apod.HdUrl.IndexOf(APOD.VID_LINK_YT));
break;
case APOD.NasaVideoType.VIMEO:
vsrc = (Apod.Url != string.Empty) ? Apod.Url.Substring(Apod.Url.IndexOf(APOD.VID_LINK_VM)) : Apod.HdUrl.Substring(Apod.HdUrl.IndexOf(APOD.VID_LINK_VM));
break;
default:
break;
}
web.DocumentText = string.Format(DocumentText, $"https://{vsrc}{(vsrc.Contains("?") ? "&autoplay=1" : "?autoplay=1")}");
}
SetupGuiWhenCompleted();
}
}
/// <summary>
/// Create full path to local file. Put path picked from GUI together with picture filename and current date
/// </summary>
/// <param name="path">Local disk path</param>
/// <param name="apod">APOD type object</param>
/// <returns></returns>
private string CreateFullPath(string path, APOD apod)
{
string filename = apod.ApiDate.ToShortDateString() + '_' +
apod.HdUrl.Substring(apod.Url.LastIndexOf('/') + 1);
foreach (char c in Path.GetInvalidFileNameChars())
filename = filename.Replace(c, '_');
return $"{path}\\{filename}";
}
//Download progress bar - event handler
private void PictureBox_LoadProgressChanged(object s, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
statusBar.Text = $"Getting NASA picture of the day... {e.ProgressPercentage}%";
//Draw progress circle over tray icon, only if minimized
//if (e.ProgressPercentage < 100 && e.ProgressPercentage > 0 && formHidden)
// using (Bitmap bmp = new Bitmap(32, 32))
// using (Graphics gfx = Graphics.FromImage(bmp))
// using (Pen p = new Pen(new LinearGradientBrush(gfx.VisibleClipBounds,
// Color.FromArgb(0, 57, 146), Color.FromArgb(255, 57, 21), 90),
// gfx.VisibleClipBounds.Width / 4))
// {
// gfx.Clear(Color.Transparent);
// gfx.SmoothingMode = SmoothingMode.AntiAlias;
// gfx.DrawArc(p,
// p.Width / 2, p.Width / 2,
// gfx.VisibleClipBounds.Width - p.Width, gfx.VisibleClipBounds.Height - p.Width,
// -90, 360 * e.ProgressPercentage / 100);
// try
// { trayIcon.Icon = Icon.FromHandle(bmp.GetHicon()); }
// catch (Exception ex){ Log("tray icon from handle error"); Log(ex.Message); }
// }
//else
// try
// { trayIcon.Icon = Properties.Resources.NASA; }
// catch (Exception ex){ Log("tray icon from resource error"); Log(ex.Message); }
}
//Download completed event - do rest of the logic - actual wallpapering and saving to disk
private void PictureBox_LoadCompleted(object s, AsyncCompletedEventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
//Setup picture box properties
pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox.Visible = true;
//Save the image to disk - required to set the wallpaper
//even if "save to disk" is not set in GUI
SaveToDisk();
//Before setting the wallpaper, we have to build full path of TEMP.JPG
//but only if not saving to custom path, because then it was already built
//if (!checkSaveToDisk.Checked)
//imagePath = $"{Assembly.GetEntryAssembly().Location.Substring(0,Assembly.GetEntryAssembly().Location.LastIndexOf("\\") + 1)}temp.jpg";
//Do actual wallpapering
Log("Wallpapering...");
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);
Log($"registry file BEFORE = {key.GetValue("Wallpaper")}");
key.SetValue(@"Wallpaper", ImagePath);
key.SetValue(@"WallpaperStyle", 6.ToString()); //always fit image to screen (zoom mode)
key.SetValue(@"TileWallpaper", 0.ToString()); //do not tile
key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true);
key.SetValue(@"Background", "0 0 0"); //black desktop around the wallpaper
Log("registry set...");
//Save wallpaper proc output
int _out = SystemParametersInfo(SPI_SETDESKWALLPAPER, //action
0, //parm
ImagePath, //parm
SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE); //winini
Log($"Wallpapering result = {_out}");
Log($"error msg = {GetLastError()}");
key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);
Log($"registry file AFTER = {key.GetValue("Wallpaper")}");
//Setup UI elements
//Activate tray menu and then enable/disable 'previous' and 'next' buttons, depending on the API date
SetupGuiWhenCompleted();
//TODO: Sometimes wallpapering does not work - why? This happens in Win7 only
if (_out == 0)
statusBar.Text = "Downloaded but not set :(";
else
{
statusBar.Text = "Done!";
listDebug.Items[8].SubItems[1].Text = new FileInfo(ImagePath).Length / 1024 + " kB";
Log($"Wallpapering succesful, downloaded img size = {listDebug.Items[8].SubItems[1].Text}");
}
//Get average color from picture - to use it later in custom menu draw routine
AvgColor = SystemColors.ActiveCaption; //use system color by default and then calc image average if possible
if (pictureBox.Image != null)
{
int r = 0;
int g = 0;
int b = 0;
int cnt = 0;
using (Bitmap bmp = new(pictureBox.Image))
//Crop the image by 1/8 in both directions (there's usually dark frame or background there)
for (int x = bmp.Width / 8; x < bmp.Width * 7 / 8; x += bmp.Width / 16)
for (int y = bmp.Height / 8; y < bmp.Height * 7 / 8; y += bmp.Height / 16)
{
AvgColor = bmp.GetPixel(x, y); //use avg as temporary storage
if (AvgColor.GetBrightness() > 0.35 && AvgColor.GetSaturation() > 0.35) //brightness treshold
{
r += AvgColor.R;
g += AvgColor.G;
b += AvgColor.B;
cnt++;
}
}
if (cnt > 0) AvgColor = Color.FromArgb(r / cnt, g / cnt, b / cnt); //in case nothing went above brightness threshold
else AvgColor = SystemColors.ActiveCaption;
}
//Invalidate picture box to force redrawing, just in case
pictureBox.Invalidate();
Apod.IsDownloading = false;
}
/// <summary>
/// Enable or disable GUI/tray items depending on current API date
/// </summary>
private void SetupGuiWhenCompleted()
{
Log(MethodBase.GetCurrentMethod().Name);
tabImage.Focus();
//Set image title
//tray icon won't take more that 63 chars
trayIcon.Text = (Apod.Title.Length > 63) ? Apod.Title.Substring(0, 63) : Apod.Title;
labelImageDesc.Text = Apod.Title;
//Copyright is not always there
if (Apod.Copyright != string.Empty)
labelImageDesc.Text += $"{Environment.NewLine}© {Apod.Copyright.Replace("\n", " ")}";
//Tray ballon
trayIcon.BalloonTipTitle = "NASA Astronomy Picture of the Day";
trayIcon.BalloonTipText = Apod.Title + ((Apod.IsImage) ? "" : " (non-image)");
if (trayIcon.BalloonTipText != string.Empty)
trayIcon.ShowBalloonTip(1);
//Image description and URL
textBoxImgDesc.Text = Apod.Explanation;
if (Apod.HdUrl != string.Empty)
textURL.Text = Apod.HdUrl;
else if (Apod.Url != string.Empty)
textURL.Text = Apod.Url;
else
textURL.Text = string.Empty;
buttonCopyLink.Enabled = (textURL.Text != string.Empty);
if (!Apod.IsImage)
{
buttonCopyImage.Enabled = false;
progressBar.Value = 100;
statusBar.Text = "Done!";
}
else
buttonCopyImage.Enabled = true;
Text = $"NASA Astronomy Picture of the Day - {Apod.ApiDate.ToShortDateString()}{((Apod.Title != string.Empty) ? $" - {Apod.Title}" : string.Empty)}";
textDate.ForeColor = SystemColors.ControlText;
textDate.Text = Apod.ApiDate.ToShortDateString();
buttonPickDate.Enabled = true;
trayIcon.ContextMenu.MenuItems["menuToday"].Enabled = true;
trayIcon.ContextMenu.MenuItems["menuExit"].Enabled = true;
//Enable/disable 'previous' and 'next' buttons, depending on the API date
//TODAY - previous enabled, today enabled, next disabled
if (Apod.ApiDate == DateTime.Today)
{
//Window buttons
buttonPrev.Enabled = true;
buttonToday.Enabled = false;
buttonNext.Text = "NEXT >>"; //←→►◄
buttonNext.Enabled = false;
buttonRefresh.Enabled = true;
//Try to get previous title from api
setupMenuItem("menuPrev", Apod.ApiDate.AddDays(-1));
trayIcon.ContextMenu.MenuItems["menuNext"].Enabled = false;
trayIcon.ContextMenu.MenuItems["menuNext"].Text = "Next - not available";
trayIcon.ContextMenu.MenuItems["menuToday"].Text = string.Join(" - ", "Today", Apod.Date, Apod.Title);
}
else
//EARLIER - all enabled, or disable previous if minimum date reached
{
//Window buttons
if (Apod.ApiDate <= DateTime.Parse("1995-06-16"))
buttonPrev.Enabled = false;
else
buttonPrev.Enabled = true;
buttonToday.Enabled = true;
buttonNext.Enabled = true;
buttonRefresh.Enabled = true;
//Try to get today, previous and next title from api
//PREVIOUS
if (Apod.ApiDate <= DateTime.Parse("1995-06-16"))
{
trayIcon.ContextMenu.MenuItems["menuPrev"].Enabled = false;
trayIcon.ContextMenu.MenuItems["menuPrev"].Text = "Previous";
}
else
{
setupMenuItem("menuPrev", Apod.ApiDate.AddDays(-1));
}
//NEXT
setupMenuItem("menuNext", Apod.ApiDate.AddDays(1));
//TODAY
setupMenuItem("menuToday", DateTime.Today);
}
//Setup menu item label with api title for desired date
void setupMenuItem(string menuItem, DateTime date)
{
try
{
using APOD a = new(date);
trayIcon.ContextMenu.MenuItems[menuItem].Enabled = true;
trayIcon.ContextMenu.MenuItems[menuItem].Text =
string.Join(" - ", menuItem.Substring(4), a.Date, a.Title + ((a.IsImage) ? "" : " (video)"));
}
catch (Exception)
{
trayIcon.ContextMenu.MenuItems[menuItem].Enabled = false;
trayIcon.ContextMenu.MenuItems[menuItem].Text = $"{menuItem.Substring(4)} - not available";
switch (menuItem)
{
case "menuPrev":
break;
case "menuToday":
break;
case "menuNext":
break;
default:
break;
}
//statusBar.Text = e.Message; //not really necessary to display this
}
}
}
/// <summary>
/// Save current image to disk
/// </summary>
private void SaveToDisk()
{
Log(MethodBase.GetCurrentMethod().Name);
//First build custom path if desired and possible
if (PathToSave != string.Empty && Apod.HdUrl != string.Empty)
ImagePath = CreateFullPath(PathToSave, Apod);
//Do actual saving
try
{
pictureBox.Image.Save(ImagePath);
}
catch (Exception e)
{
Log(e.Message);
statusBar.Text = e.Message;
}
}
//Timer event handler - reload image with today date
private void TimerRefresh_Tick(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
Log("--- TIMER TICK! ---");
//Reload image only if today date has changed
if (DateTime.Today != Apod.ApiDate)
{
Log("AUTO REFRESH - date rolled over");
Log($"API date = {Apod.ApiDate}");
Log($"TODAY date = {DateTime.Today}");
SetApodDate(Apod, DateTime.Today);
GetNasaApod();
}
else
Log("Date not changed, nothing to do in auto-refresh end");
}
//Auto refresh checkbox - enable or disable automatic refresh
private void CheckAutoRefresh_CheckedChanged(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (checkAutoRefresh.Checked)
timerRefresh.Enabled = true;
else
timerRefresh.Enabled = false;
IniFile.Write("autoRefresh", checkAutoRefresh.Checked.ToString());
}
//Save to custom path checkbox
private void CheckSaveToDisk_CheckedChanged(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
//Saving to file is always active - we need image file to set the wallpaper
//It is file path that we will manipulate here
if (checkSaveToDisk.Checked) //save to disak enabled
{
//Enable path selection [...] button and file path text box
buttonPath.Enabled = true;
textPath.Enabled = true;
//Is it first click? Custom path would be null in this case
if (textPath.Text == null || textPath.Text == string.Empty)
{
ButtonPath_Click(this, e); //invoke path selection dialog
if (dialogPath.SelectedPath == string.Empty) //if dialog cancelled, revert UI changes
{
buttonPath.Enabled = false;
textPath.Enabled = false;
checkSaveToDisk.Checked = false;
}
}
else
PathToSave = textPath.Text;
}
else //save to disk disabled
{
buttonPath.Enabled = false;
textPath.Enabled = false;
PathToSave = string.Empty;
ImagePath = @".\temp.jpg";
}
IniFile.Write("saveToDisk", checkSaveToDisk.Checked.ToString());
IniFile.Write("pathToSave", PathToSave);
}
//Custom path selection button
private void ButtonPath_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
dialogPath.ShowDialog(); //display path selection dialog
if (dialogPath.SelectedPath != string.Empty)
{
PathToSave = dialogPath.SelectedPath; //save the path
textPath.Text = PathToSave; //and display it in path text box
IniFile.Write("saveToDisk", checkSaveToDisk.Checked.ToString());
IniFile.Write("pathToSave", PathToSave);
}
}
//Copy link to clipboard
private void ButtonCopyLink_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (textURL.Text != string.Empty)
Clipboard.SetText(textURL.Text);
}
//Copy image to clipboard
private void ButtonCopyImage_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (pictureBox.Image != null)
Clipboard.SetImage(pictureBox.Image);
}
//Tray icon click - hide/show window
private void MyIcon_MouseClick(object s, MouseEventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
//Left click only
if (e.Button == MouseButtons.Left)
//Toggle window state
if (FormHidden)
{
Show();
WindowState = FormWindowState.Normal;
FormHidden = false;
}
else
{
Hide();
FormHidden = true;
}
}
//Minimize to system tray
private void WindowResize(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (WindowState == FormWindowState.Minimized)
{
Hide();
FormHidden = true;
trayIcon.BalloonTipTitle = "NASA Astronomy Picture of the Day";
trayIcon.BalloonTipText = (trayIcon.Text == string.Empty) ? "No image today :(" : trayIcon.Text;
trayIcon.ShowBalloonTip(1);
}
}
//Previous button click
private void ButtonPrev_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
SetApodDate(Apod, Apod.ApiDate.AddDays(-1));
GetNasaApod();
}
//Next button click
private void ButtonNext_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (Apod.ApiDate < DateTime.Today)
{
SetApodDate(Apod, Apod.ApiDate.AddDays(1));
GetNasaApod();
}
}
//Today button click
private void ButtonToday_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
SetApodDate(Apod, DateTime.Today);
GetNasaApod();
}
//Refresh button - simply reload current image
private void ButtonRefresh_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
SetApodDate(Apod, Apod.ApiDate);
GetNasaApod();
}
//EXPERIMANTAL - draw the title over the picture
//private void pictureBox_Paint(object s, PaintEventArgs e)
//{
// Log(MethodBase.GetCurrentMethod().Name);
/*
//get picture box size
Size picSize = pictureBox.Size;
//find maximum font size for current picture box size
//(it will be ususally pic box width that determines max size)
//initial font - consolas bold, 1pt
int fs = 12;
Font myFont = new Font("Consolas", fs, FontStyle.Bold);
Size textSize;
//now loop over font sizes
do
{
myFont = new Font("Consolas", ++fs, FontStyle.Bold);
textSize = TextRenderer.MeasureText(myIcon.Text, myFont);
}
//stop when text size is bigger than picture box size
while (textSize.Width < picSize.Width * 0.75 &&
textSize.Height < picSize.Height * 0.35);
myFont = new Font("Consolas", --fs, FontStyle.Bold);
//draw text, center it using it's own size
//(half image widht and height, then subtract half of text's widht and height)
e.Graphics.DrawString(myIcon.Text, //text to show
myFont, //font to use
Brushes.Yellow, //text color (brush)
pictureBox.Width/2 - //text X position
TextRenderer.MeasureText(myIcon.Text, myFont).Width / 2,
pictureBox.Height * (float)(0.90) - //text Y position
TextRenderer.MeasureText(myIcon.Text, myFont).Height / 2);
myFont.Dispose(); //release font, don't need it anymore
*/
//}
//Pick a date - show calendar
private void ButtonPickDate_Click(object s, EventArgs e)
{
Log(MethodBase.GetCurrentMethod().Name);
if (!Calendar.Visible)
{
Calendar.ShowTodayCircle = true;
Calendar.BringToFront();
Calendar.SetDate(Apod.ApiDate);
Calendar.Visible = true;
}
else
Calendar.Visible = false;
}
//Calendar click - set the date and get the image at once
private void Calendar_DateSelected(object s, DateRangeEventArgs e)