not1mm 24.3.16__py3-none-any.whl → 24.3.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. not1mm/__main__.py +288 -124
  2. not1mm/bandmap.py +207 -191
  3. not1mm/checkwindow.py +62 -109
  4. not1mm/data/MASTER.SCP +1077 -749
  5. not1mm/data/bandmap.ui +4 -4
  6. not1mm/data/checkwindow.ui +9 -16
  7. not1mm/data/cty.json +1 -1
  8. not1mm/data/logwindow.ui +16 -11
  9. not1mm/data/main.ui +16 -6
  10. not1mm/data/vfo.ui +64 -52
  11. not1mm/fsutils.py +63 -0
  12. not1mm/lib/about.py +4 -2
  13. not1mm/lib/cat_interface.py +1 -1
  14. not1mm/lib/cwinterface.py +1 -1
  15. not1mm/lib/database.py +4 -4
  16. not1mm/lib/edit_contact.py +2 -2
  17. not1mm/lib/edit_macro.py +2 -3
  18. not1mm/lib/edit_opon.py +2 -2
  19. not1mm/lib/edit_station.py +3 -3
  20. not1mm/lib/ham_utility.py +1 -1
  21. not1mm/lib/lookup.py +1 -1
  22. not1mm/lib/multicast.py +9 -4
  23. not1mm/lib/n1mm.py +1 -1
  24. not1mm/lib/new_contest.py +2 -2
  25. not1mm/lib/select_contest.py +2 -2
  26. not1mm/lib/settings.py +3 -4
  27. not1mm/lib/super_check_partial.py +8 -5
  28. not1mm/lib/version.py +1 -1
  29. not1mm/logwindow.py +62 -105
  30. not1mm/plugins/10_10_fall_cw.py +1 -1
  31. not1mm/plugins/10_10_spring_cw.py +1 -1
  32. not1mm/plugins/10_10_summer_phone.py +1 -1
  33. not1mm/plugins/10_10_winter_phone.py +1 -1
  34. not1mm/plugins/arrl_10m.py +1 -1
  35. not1mm/plugins/arrl_dx_cw.py +1 -1
  36. not1mm/plugins/arrl_dx_ssb.py +1 -1
  37. not1mm/plugins/arrl_field_day.py +1 -1
  38. not1mm/plugins/arrl_ss_cw.py +1 -1
  39. not1mm/plugins/arrl_ss_phone.py +1 -1
  40. not1mm/plugins/arrl_vhf_jan.py +1 -1
  41. not1mm/plugins/arrl_vhf_jun.py +1 -1
  42. not1mm/plugins/arrl_vhf_sep.py +1 -1
  43. not1mm/plugins/canada_day.py +1 -1
  44. not1mm/plugins/cq_160_cw.py +1 -1
  45. not1mm/plugins/cq_160_ssb.py +1 -1
  46. not1mm/plugins/cq_wpx_cw.py +1 -1
  47. not1mm/plugins/cq_wpx_ssb.py +1 -1
  48. not1mm/plugins/cq_ww_cw.py +1 -1
  49. not1mm/plugins/cq_ww_ssb.py +1 -1
  50. not1mm/plugins/cwt.py +1 -1
  51. not1mm/plugins/general_logging.py +1 -1
  52. not1mm/plugins/iaru_hf.py +1 -1
  53. not1mm/plugins/jidx_cw.py +1 -1
  54. not1mm/plugins/jidx_ph.py +1 -1
  55. not1mm/plugins/naqp_cw.py +1 -1
  56. not1mm/plugins/naqp_ssb.py +1 -1
  57. not1mm/plugins/phone_weekly_test.py +1 -1
  58. not1mm/plugins/stew_perry_topband.py +1 -1
  59. not1mm/plugins/winter_field_day.py +1 -1
  60. not1mm/vfo.py +78 -98
  61. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/METADATA +67 -55
  62. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/RECORD +68 -66
  63. testing/detectdark.py +35 -0
  64. testing/test.py +13 -11
  65. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/LICENSE +0 -0
  66. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/WHEEL +0 -0
  67. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/entry_points.txt +0 -0
  68. {not1mm-24.3.16.dist-info → not1mm-24.3.24.dist-info}/top_level.txt +0 -0
not1mm/bandmap.py CHANGED
@@ -6,56 +6,27 @@ GPL V3
6
6
  """
7
7
 
8
8
  # pylint: disable=unused-import, c-extension-no-member, no-member, invalid-name, too-many-lines
9
- # pylint: disable=logging-fstring-interpolation, line-too-long
10
-
11
- from datetime import datetime, timezone
12
- from decimal import Decimal
13
- from json import loads
14
- from pathlib import Path
9
+ # pylint: disable=logging-fstring-interpolation, line-too-long, no-name-in-module
15
10
 
16
11
  import logging
17
12
  import os
18
-
19
- # import pkgutil
20
13
  import platform
21
- import sys
22
14
  import sqlite3
15
+ from datetime import datetime, timezone
16
+ from decimal import Decimal
17
+ from json import loads
23
18
 
24
- from PyQt5 import QtCore, QtGui
25
- from PyQt5 import QtNetwork
26
- from PyQt5 import QtWidgets, uic
19
+ from PyQt5 import QtCore, QtGui, QtWidgets, uic, QtNetwork
20
+ from PyQt5.QtCore import Qt
27
21
 
22
+ import not1mm.fsutils as fsutils
28
23
  from not1mm.lib.multicast import Multicast
29
24
 
30
- os.environ["QT_QPA_PLATFORMTHEME"] = "gnome"
25
+ logger = logging.getLogger(__name__)
31
26
 
32
27
  PIXELSPERSTEP = 10
33
28
  UPDATE_INTERVAL = 2000
34
29
 
35
- # DeprecationWarning: 'pkgutil.get_loader' is deprecated and slated for removal in Python 3.14
36
- # loader = pkgutil.get_loader("not1mm")
37
- # WORKING_PATH = os.path.dirname(loader.get_filename())
38
- WORKING_PATH = os.path.dirname(__loader__.get_filename())
39
-
40
- if "XDG_DATA_HOME" in os.environ:
41
- DATA_PATH = os.environ.get("XDG_DATA_HOME")
42
- else:
43
- DATA_PATH = str(Path.home() / ".local" / "share")
44
- DATA_PATH += "/not1mm"
45
-
46
- if "XDG_CONFIG_HOME" in os.environ:
47
- CONFIG_PATH = os.environ.get("XDG_CONFIG_HOME")
48
- else:
49
- CONFIG_PATH = str(Path.home() / ".config")
50
- CONFIG_PATH += "/not1mm"
51
-
52
- DARK_STYLESHEET = ""
53
-
54
- PREF = {}
55
- if os.path.exists(CONFIG_PATH + "/not1mm.json"):
56
- with open(CONFIG_PATH + "/not1mm.json", "rt", encoding="utf-8") as file_descriptor:
57
- PREF = loads(file_descriptor.read())
58
-
59
30
 
60
31
  class Band:
61
32
  """the band"""
@@ -108,16 +79,20 @@ class Database:
108
79
  self.db.row_factory = self.row_factory
109
80
  self.cursor = self.db.cursor()
110
81
  sql_command = (
111
- "create table spots("
112
- "ts DATETIME NOT NULL, "
82
+ "create table spots ("
113
83
  "callsign VARCHAR(15) NOT NULL, "
84
+ "ts DATETIME NOT NULL, "
114
85
  "freq DOUBLE NOT NULL, "
115
- "band VARCHAR(6), "
116
86
  "mode VARCHAR(6), "
117
87
  "spotter VARCHAR(15) NOT NULL, "
118
88
  "comment VARCHAR(45));"
119
89
  )
120
90
  self.cursor.execute(sql_command)
91
+
92
+ self.cursor.execute("CREATE INDEX spot_call_index ON spots (callsign);")
93
+ self.cursor.execute("CREATE INDEX spot_freq_index ON spots (freq);")
94
+ self.cursor.execute("CREATE INDEX spot_ts_index ON spots (ts);")
95
+
121
96
  self.db.commit()
122
97
 
123
98
  @staticmethod
@@ -175,31 +150,27 @@ class Database:
175
150
  If False, do not delete any previous spots with the same callsign.
176
151
  Default is True.
177
152
 
178
-
179
153
  Returns
180
154
  -------
181
155
  Nothing.
182
156
  """
183
157
  try:
184
158
  if erase:
185
- delete_call = (
186
- f"delete from spots where callsign = '{spot.get('callsign')}';"
187
- )
188
- self.cursor.execute(delete_call)
159
+ delete_call = "delete from spots where callsign = ?;"
160
+ self.cursor.execute(delete_call, (spot.get("callsign"),))
189
161
  self.db.commit()
190
162
 
191
- pre = "INSERT INTO spots("
192
- values = []
193
- columns = ""
194
- placeholders = ""
195
- for key in spot.keys():
196
- columns += f"{key},"
197
- values.append(spot[key])
198
- placeholders += "?,"
199
- post = f") VALUES({placeholders[:-1]});"
200
-
201
- sql = f"{pre}{columns[:-1]}{post}"
202
- self.cursor.execute(sql, tuple(values))
163
+ self.cursor.execute(
164
+ "INSERT INTO spots(callsign, ts, freq, mode, spotter, comment) VALUES(?, ?, ?, ?, ?, ?)",
165
+ (
166
+ spot["callsign"],
167
+ spot["ts"],
168
+ spot["freq"],
169
+ spot.get("mode", None),
170
+ spot["spotter"],
171
+ spot.get("comment", None),
172
+ ),
173
+ )
203
174
  self.db.commit()
204
175
  except sqlite3.IntegrityError:
205
176
  ...
@@ -239,7 +210,8 @@ class Database:
239
210
  A list of dicts.
240
211
  """
241
212
  self.cursor.execute(
242
- f"select * from spots where freq >= {start} and freq <= {end} order by freq ASC;"
213
+ "select * from spots where freq >= ? and freq <= ? order by freq ASC;",
214
+ (start, end),
243
215
  )
244
216
  return self.cursor.fetchall()
245
217
 
@@ -261,7 +233,8 @@ class Database:
261
233
  """
262
234
 
263
235
  self.cursor.execute(
264
- f"select * from spots where freq > {current} and freq <= {limit} order by freq ASC;"
236
+ "select * from spots where freq > ? and freq <= ? order by freq ASC;",
237
+ ({current}, limit),
265
238
  )
266
239
  return self.cursor.fetchone()
267
240
 
@@ -284,8 +257,8 @@ class Database:
284
257
  """
285
258
 
286
259
  self.cursor.execute(
287
- f"select * from spots where freq >= {start} "
288
- f"and freq <= {end} and callsign like '%{dx}%';"
260
+ "select * from spots where freq >= ? and freq <= ? and callsign like ?;",
261
+ (start, end, f"%{dx}%"),
289
262
  )
290
263
  return self.cursor.fetchone()
291
264
 
@@ -305,7 +278,8 @@ class Database:
305
278
  A list of dicts.
306
279
  """
307
280
  self.cursor.execute(
308
- f"select * from spots where freq < {current} and freq >= {limit} order by freq DESC;"
281
+ "select * from spots where freq < ? and freq >= ? order by freq DESC;",
282
+ (current, limit),
309
283
  )
310
284
  return self.cursor.fetchone()
311
285
 
@@ -323,14 +297,13 @@ class Database:
323
297
  None
324
298
  """
325
299
  self.cursor.execute(
326
- f"delete from spots where ts < datetime('now', '-{minutes} minutes');"
300
+ "delete from spots where ts < datetime('now', ?);",
301
+ (f"-{minutes} minutes",),
327
302
  )
328
303
 
329
304
 
330
- class MainWindow(QtWidgets.QMainWindow):
331
- """
332
- The main window
333
- """
305
+ class BandMapWindow(QtWidgets.QDockWidget):
306
+ """The BandMapWindow class."""
334
307
 
335
308
  zoom = 5
336
309
  currentBand = Band("20m")
@@ -346,14 +319,14 @@ class MainWindow(QtWidgets.QMainWindow):
346
319
  bandwidth_mark = []
347
320
  worked_list = {}
348
321
  multicast_interface = None
322
+ text_color = QtGui.QColor(45, 45, 45)
349
323
 
350
324
  def __init__(self, *args, **kwargs):
351
325
  super().__init__(*args, **kwargs)
352
326
  self._udpwatch = None
353
- data_path = WORKING_PATH + "/data/bandmap.ui"
354
- uic.loadUi(data_path, self)
355
- if PREF.get("dark_mode"):
356
- self.setStyleSheet(DARK_STYLESHEET)
327
+
328
+ uic.loadUi(fsutils.APP_DATA_PATH / "bandmap.ui", self)
329
+ self.settings = self.get_settings()
357
330
  self.agetime = self.clear_spot_olderSpinBox.value()
358
331
  self.clear_spot_olderSpinBox.valueChanged.connect(self.spot_aging_changed)
359
332
  self.clearButton.clicked.connect(self.clear_spots)
@@ -375,35 +348,75 @@ class MainWindow(QtWidgets.QMainWindow):
375
348
  self.update_timer = QtCore.QTimer()
376
349
  self.update_timer.timeout.connect(self.update_station_timer)
377
350
  self.update_timer.start(UPDATE_INTERVAL)
351
+ self.setDarkMode(self.settings.get("darkmode", False))
378
352
  self.update()
379
353
  self.multicast_interface = Multicast(
380
- PREF.get("multicast_group", "239.1.1.1"),
381
- PREF.get("multicast_port", 2239),
382
- PREF.get("interface_ip", "0.0.0.0"),
354
+ self.settings.get("multicast_group", "239.1.1.1"),
355
+ self.settings.get("multicast_port", 2239),
356
+ self.settings.get("interface_ip", "0.0.0.0"),
383
357
  )
384
358
  self.multicast_interface.ready_read_connect(self.watch_udp)
385
359
  self.request_workedlist()
360
+ self.request_contest()
361
+
362
+ def get_settings(self) -> dict:
363
+ """Get the settings."""
364
+ if os.path.exists(fsutils.CONFIG_FILE):
365
+ with open(fsutils.CONFIG_FILE, "rt", encoding="utf-8") as file_descriptor:
366
+ return loads(file_descriptor.read())
367
+
368
+ def setDarkMode(self, setdarkmode=False):
369
+ """testing"""
370
+
371
+ if setdarkmode:
372
+ darkPalette = QtGui.QPalette()
373
+ darkColor = QtGui.QColor(45, 45, 45)
374
+ self.text_color = Qt.white
375
+ disabledColor = QtGui.QColor(127, 127, 127)
376
+ darkPalette.setColor(QtGui.QPalette.Window, darkColor)
377
+ darkPalette.setColor(QtGui.QPalette.WindowText, Qt.white)
378
+ darkPalette.setColor(QtGui.QPalette.Base, QtGui.QColor(18, 18, 18))
379
+ darkPalette.setColor(QtGui.QPalette.AlternateBase, darkColor)
380
+ darkPalette.setColor(QtGui.QPalette.Text, Qt.white)
381
+ darkPalette.setColor(
382
+ QtGui.QPalette.Disabled, QtGui.QPalette.Text, disabledColor
383
+ )
384
+ darkPalette.setColor(QtGui.QPalette.Button, darkColor)
385
+ darkPalette.setColor(QtGui.QPalette.ButtonText, Qt.white)
386
+ darkPalette.setColor(
387
+ QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, disabledColor
388
+ )
389
+ darkPalette.setColor(QtGui.QPalette.BrightText, Qt.red)
390
+ darkPalette.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
391
+ darkPalette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
392
+ darkPalette.setColor(QtGui.QPalette.HighlightedText, Qt.black)
393
+ darkPalette.setColor(
394
+ QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, disabledColor
395
+ )
386
396
 
387
- def quit_app(self):
388
- """doc"""
389
- app.quit()
397
+ self.setPalette(darkPalette)
398
+ self.callsignField.setPalette(darkPalette)
399
+ self.update()
400
+ else:
401
+ palette = self.style().standardPalette()
402
+ self.setPalette(palette)
403
+ self.callsignField.setPalette(palette)
404
+ self.text_color = Qt.black
405
+ self.update()
390
406
 
391
407
  def connect(self):
392
- """doc"""
408
+ """Connect to the cluster."""
409
+ if not self.callsignField.text():
410
+ self.callsignField.setFocus()
411
+ return
393
412
  if self.connected is True:
394
- self.socket.close()
395
- self.connected = False
396
- self.connectButton.setStyleSheet("color: red;")
397
- self.connectButton.setText("Closed")
413
+ self.close_cluster()
398
414
  return
399
- if os.path.exists(CONFIG_PATH + "/not1mm.json"):
400
- with open(
401
- CONFIG_PATH + "/not1mm.json", "rt", encoding="utf-8"
402
- ) as _file_descriptor:
403
- globals()["PREF"] = loads(_file_descriptor.read())
404
- server = PREF.get("cluster_server", "dxc.nc7j.com")
405
- port = PREF.get("cluster_port", 7373)
406
- logger.debug("%s", f"{server} {port}")
415
+ # refresh settings
416
+ self.settings = self.get_settings()
417
+ server = self.settings.get("cluster_server", "dxc.nc7j.com")
418
+ port = self.settings.get("cluster_port", 7373)
419
+ logger.info(f"connecting to dx cluster {server} {port}")
407
420
  self.socket.connectToHost(server, port)
408
421
  self.connectButton.setStyleSheet("color: white;")
409
422
  self.connectButton.setText("Connecting")
@@ -514,8 +527,13 @@ class MainWindow(QtWidgets.QMainWindow):
514
527
  cmd["spots"] = []
515
528
  self.multicast_interface.send_as_json(cmd)
516
529
  continue
517
- if packet.get("cmd", "") == "HALT":
518
- self.quit_app()
530
+ if packet.get("cmd", "") == "CONTESTSTATUS":
531
+ if not self.callsignField.text():
532
+ self.callsignField.setText(packet.get("operator", "").upper())
533
+ self.callsignField.selectAll()
534
+ continue
535
+ if packet.get("cmd", "") == "DARKMODE":
536
+ self.setDarkMode(packet.get("state", False))
519
537
 
520
538
  def spot_clicked(self):
521
539
  """dunno"""
@@ -536,13 +554,23 @@ class MainWindow(QtWidgets.QMainWindow):
536
554
  cmd["station"] = platform.node()
537
555
  self.multicast_interface.send_as_json(cmd)
538
556
 
557
+ def request_contest(self):
558
+ """Request active contest from logger"""
559
+ cmd = {}
560
+ cmd["cmd"] = "GETCONTESTSTATUS"
561
+ cmd["station"] = platform.node()
562
+ self.multicast_interface.send_as_json(cmd)
563
+
539
564
  def update_station_timer(self):
540
565
  """doc"""
541
566
  self.update_stations()
542
567
 
543
568
  def update(self):
544
569
  """doc"""
545
- self.update_timer.setInterval(UPDATE_INTERVAL)
570
+ try:
571
+ self.update_timer.setInterval(UPDATE_INTERVAL)
572
+ except AttributeError:
573
+ ...
546
574
  self.clear_all_callsign_from_scene()
547
575
  self.clear_freq_mark(self.rxMark)
548
576
  self.clear_freq_mark(self.txMark)
@@ -562,12 +590,13 @@ class MainWindow(QtWidgets.QMainWindow):
562
590
  i * PIXELSPERSTEP,
563
591
  length + 10,
564
592
  i * PIXELSPERSTEP,
565
- QtGui.QPen(QtGui.QColor(192, 192, 192)),
593
+ QtGui.QPen(self.text_color),
566
594
  )
567
595
  if i % 5 == 0: # Add Frequency
568
596
  freq = self.currentBand.start + step * i
569
597
  text = f"{freq:.3f}"
570
598
  self.something = self.bandmap_scene.addText(text)
599
+ self.something.setDefaultTextColor(self.text_color)
571
600
  self.something.setPos(
572
601
  -(self.something.boundingRect().width()) + 10,
573
602
  i * PIXELSPERSTEP - (self.something.boundingRect().height() / 2),
@@ -610,7 +639,7 @@ class MainWindow(QtWidgets.QMainWindow):
610
639
 
611
640
  def Freq2ScenePos(self, freq: float):
612
641
  """doc"""
613
- if freq < self.currentBand.start or freq > self.currentBand.end:
642
+ if not freq or freq < self.currentBand.start or freq > self.currentBand.end:
614
643
  return QtCore.QPointF()
615
644
  step, _digits = self.determine_step_digits()
616
645
  ret = QtCore.QPointF(
@@ -641,7 +670,7 @@ class MainWindow(QtWidgets.QMainWindow):
641
670
  # anim.setEndValue(int(freq_pos - (self.height() / 2) + 80))
642
671
  # anim.start(QtCore.QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
643
672
 
644
- def drawfreqmark(self, freq, _step, color, currentPolygon):
673
+ def drawfreqmark(self, freq, _step, color, currentPolygon) -> None:
645
674
  """doc"""
646
675
 
647
676
  self.clear_freq_mark(currentPolygon)
@@ -660,7 +689,7 @@ class MainWindow(QtWidgets.QMainWindow):
660
689
  brush = QtGui.QBrush(color)
661
690
  currentPolygon.append(self.bandmap_scene.addPolygon(poly, pen, brush))
662
691
 
663
- def draw_bandwidth(self, freq, _step, color, currentPolygon):
692
+ def draw_bandwidth(self, freq, _step, color, currentPolygon) -> None:
664
693
  """bandwidth"""
665
694
  logger.debug("%s", f"mark:{currentPolygon} f:{freq} b:{self.bandwidth}")
666
695
  self.clear_freq_mark(currentPolygon)
@@ -687,16 +716,23 @@ class MainWindow(QtWidgets.QMainWindow):
687
716
  def update_stations(self):
688
717
  """doc"""
689
718
  self.update_timer.setInterval(UPDATE_INTERVAL)
719
+ if not self.connected:
720
+ return
721
+
690
722
  self.clear_all_callsign_from_scene()
691
723
  self.spot_aging()
692
724
  step, _digits = self.determine_step_digits()
693
725
 
694
726
  result = self.spots.getspotsinband(self.currentBand.start, self.currentBand.end)
727
+ logger.debug(
728
+ f"{len(result)} spots in range {self.currentBand.start} - {self.currentBand.end}"
729
+ )
730
+
695
731
  entity = ""
696
732
  if result:
697
733
  min_y = 0.0
698
734
  for items in result:
699
- pen_color = QtGui.QColor(192, 192, 192)
735
+ pen_color = self.text_color
700
736
  if items.get("comment") == "MARKED":
701
737
  pen_color = QtGui.QColor(47, 47, 255)
702
738
  if items.get("callsign") in self.worked_list:
@@ -754,23 +790,22 @@ class MainWindow(QtWidgets.QMainWindow):
754
790
 
755
791
  return (step, digits)
756
792
 
757
- def set_band(self, band: str, savePrevBandZoom: bool):
758
- """doc"""
793
+ def set_band(self, band: str, savePrevBandZoom: bool) -> None:
794
+ """Change band being shown."""
759
795
  logger.debug("%s", f"{band} {savePrevBandZoom}")
760
796
  if band != self.currentBand.name:
761
797
  if savePrevBandZoom:
762
798
  self.saveCurrentZoom()
763
799
  self.currentBand = Band(band)
764
- # self.zoom = self.savedZoom(band)
765
800
  self.update()
766
801
 
767
- def spot_aging(self):
768
- """doc"""
802
+ def spot_aging(self) -> None:
803
+ """Delete spots older than age time."""
769
804
  if self.agetime:
770
805
  self.spots.delete_spots(self.agetime)
771
806
 
772
- def clear_all_callsign_from_scene(self):
773
- """doc"""
807
+ def clear_all_callsign_from_scene(self) -> None:
808
+ """Remove callsigns from the scene."""
774
809
  for items in self.textItemList:
775
810
  self.bandmap_scene.removeItem(items)
776
811
  self.textItemList.clear()
@@ -778,70 +813,69 @@ class MainWindow(QtWidgets.QMainWindow):
778
813
  self.bandmap_scene.removeItem(items)
779
814
  self.lineitemlist.clear()
780
815
 
781
- def clear_freq_mark(self, currentPolygon):
782
- """doc"""
816
+ def clear_freq_mark(self, currentPolygon) -> None:
817
+ """Remove frequency marks from the scene."""
783
818
  if currentPolygon:
784
819
  for mark in currentPolygon:
785
820
  self.bandmap_scene.removeItem(mark)
786
821
  currentPolygon.clear()
787
822
 
788
- def receive(self):
789
- """doc"""
790
- data = self.socket.readAll()
791
- data = str(data, "utf-8").strip()
792
- if "login:" in data:
793
- self.send_command(self.callsignField.text())
794
- self.send_command(PREF.get("cluster_filter", ""))
795
- self.send_command("set dx extension Section")
796
- self.send_command("set dx mode " + PREF.get("cluster_mode", "OPEN"))
797
- return
798
- if "call:" in data or "callsign:" in data:
799
- self.send_command(self.callsignField.text())
800
- self.send_command(PREF.get("cluster_filter", ""))
801
- self.send_command("set dx extension Section")
802
- self.send_command("set dx mode " + PREF.get("cluster_mode", "OPEN"))
803
- return
804
- if "DX de" in data:
805
- parts = data.split()
806
- spotter = parts[2]
807
- freq = parts[3]
808
- dx = parts[4]
809
- _time = parts[-1]
810
- comment = " ".join(parts[5:-1])
811
- # spot = DxSpot()
812
- spot = {}
813
- spot["ts"] = datetime.now(timezone.utc).isoformat(" ")[:19]
814
- spot["callsign"] = dx
815
- spot["spotter"] = spotter
816
- spot["comment"] = comment
817
- try:
818
- spot["freq"] = float(freq) / 1000
819
- except ValueError:
820
- logger.debug("%s", f"{data}")
821
- self.spots.addspot(spot)
822
- return
823
- if self.callsignField.text().upper() in data:
824
- self.connectButton.setStyleSheet("color: green;")
825
- self.connectButton.setText("Connected")
826
- logger.debug("%s", f"{data}")
827
-
828
- def maybeconnected(self):
829
- """doc"""
823
+ def receive(self) -> None:
824
+ """Process waiting bytes"""
825
+ while self.socket.bytesAvailable():
826
+ data = self.socket.readLine(1000)
827
+ data = str(data, "utf-8").strip()
828
+
829
+ if "login:" in data or "call:" in data or "callsign:" in data:
830
+ self.send_command(self.callsignField.text())
831
+ self.send_command(self.settings.get("cluster_filter", ""))
832
+ self.send_command("set dx extension Section")
833
+ self.send_command(
834
+ "set dx mode " + self.settings.get("cluster_mode", "OPEN")
835
+ )
836
+ return
837
+ if "DX de" in data:
838
+ parts = data.split()
839
+ spotter = parts[2]
840
+ freq = parts[3]
841
+ dx = parts[4]
842
+ _time = parts[-1]
843
+ comment = " ".join(parts[5:-1])
844
+ # spot = DxSpot()
845
+ spot = {}
846
+ spot["ts"] = datetime.now(timezone.utc).isoformat(" ")[:19]
847
+ spot["callsign"] = dx
848
+ spot["spotter"] = spotter
849
+ spot["comment"] = comment
850
+ logger.debug(f"{spot}")
851
+ try:
852
+ spot["freq"] = float(freq) / 1000
853
+ self.spots.addspot(spot)
854
+ except ValueError:
855
+ logger.debug(f"couldn't parse freq from datablock {data}")
856
+ return
857
+ if self.callsignField.text().upper() in data:
858
+ self.connectButton.setStyleSheet("color: green;")
859
+ self.connectButton.setText("Connected")
860
+ logger.debug(f"callsign login acknowledged {data}")
861
+
862
+ def maybeconnected(self) -> None:
863
+ """Update visual state of the connect button."""
830
864
  self.connectButton.setStyleSheet("color: yellow;")
831
865
  self.connectButton.setText("Connecting")
832
866
 
833
- def socket_error(self):
834
- """doc"""
835
- print("An Error occurred.")
867
+ def socket_error(self) -> None:
868
+ """Oopsie"""
869
+ logger.warning("An Error occurred.")
836
870
 
837
- def disconnected(self):
838
- """doc"""
871
+ def disconnected(self) -> None:
872
+ """Called when socket is disconnected."""
839
873
  self.connected = False
840
874
  self.connectButton.setStyleSheet("color: red;")
841
875
  self.connectButton.setText("Closed")
842
876
 
843
- def send_command(self, cmd: str):
844
- """doc"""
877
+ def send_command(self, cmd: str) -> None:
878
+ """Send a command to the cluster."""
845
879
  cmd += "\r\n"
846
880
  tosend = bytes(cmd, encoding="ascii")
847
881
  logger.debug("%s", f"{tosend}")
@@ -849,44 +883,26 @@ class MainWindow(QtWidgets.QMainWindow):
849
883
  if self.socket.isOpen():
850
884
  self.socket.write(tosend)
851
885
 
852
- def clear_spots(self):
853
- """doc"""
886
+ def clear_spots(self) -> None:
887
+ """Delete all spots from the database."""
854
888
  self.spots.delete_spots(0)
855
889
 
856
- def spot_aging_changed(self):
857
- """doc"""
890
+ def spot_aging_changed(self) -> None:
891
+ """Called when spot aging spinbox is changed."""
858
892
  self.agetime = self.clear_spot_olderSpinBox.value()
859
893
 
860
- def showContextMenu(self):
861
- """doc"""
862
-
863
-
864
- def run():
865
- """doc"""
866
- sys.exit(app.exec())
867
-
868
-
869
- logger = logging.getLogger("__main__")
870
- handler = logging.StreamHandler()
871
- formatter = logging.Formatter(
872
- datefmt="%H:%M:%S",
873
- fmt="[%(asctime)s] %(levelname)s %(module)s - %(funcName)s Line %(lineno)d:\n%(message)s",
874
- )
875
- handler.setFormatter(formatter)
876
- logger.addHandler(handler)
877
-
878
- if Path("./debug").exists():
879
- logger.setLevel(logging.DEBUG)
880
- logger.debug("debugging on")
881
- else:
882
- logger.setLevel(logging.WARNING)
883
- logger.warning("debugging off")
884
-
885
- app = QtWidgets.QApplication(sys.argv)
894
+ def showContextMenu(self) -> None:
895
+ """doc string for the linter"""
886
896
 
897
+ def close_cluster(self) -> None:
898
+ """Close socket connection"""
899
+ if self.socket and self.socket.isOpen():
900
+ logger.info("Closing dx cluster connection")
901
+ self.socket.close()
902
+ self.connected = False
903
+ self.connectButton.setStyleSheet("color: red;")
904
+ self.connectButton.setText("Closed")
887
905
 
888
- app.setStyle("Adwaita-Dark")
889
- window = MainWindow()
890
- window.show()
891
- if __name__ == "__main__":
892
- run()
906
+ def closeEvent(self, _event: QtGui.QCloseEvent) -> None:
907
+ """Triggered when instance closes."""
908
+ self.close_cluster()