Skip to content

Commit 0241852

Browse files
committed
Release v0.2.1: Skill Plan Manager, Smart Tooltips, and UI Refresh
1 parent 9e0cb7f commit 0241852

7 files changed

Lines changed: 117 additions & 37 deletions

File tree

Readme.md

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
# Personal Skill Monitor
22

3+
![Main window1](Screenshots/Screenshot1.png)
34

4-
![Main window1](Screenshots/Screenshot_1.png)
5-
6-
7-
8-
![Main window2](Screenshots/Screenshot_2.png)
9-
5+
![Main window2](Screenshots/Screenshot2.png)
106

117
Personal Skill Monitor is a desktop application for EVE Online that displays character skills and the skill queue via EVE ESI/SSO, with convenient filters and multi‑format export.
128

@@ -21,6 +17,16 @@ The app is aimed at capsuleers who want a quick overview of their training witho
2117
- Support for multiple characters with easy switching in the sidebar.
2218
- Refresh tokens stored locally (in `tokens.json`).
2319

20+
- **Skill Plan Manager (New!)**
21+
- Create and manage custom training plans.
22+
- **Auto-dependency resolution:** Adding a high-level skill automatically adds all required prerequisites in the correct order.
23+
- Double-click a skill in the catalog to quickly add it to your plan.
24+
- Export plans to clipboard or file.
25+
26+
- **Smart Tooltips (New!)**
27+
- Hover over any skill to see its full description.
28+
- Uses an efficient polling mechanism that works even in complex UI layouts and AppImage builds.
29+
2430
- **Skills View**
2531
- Full list of character skills with:
2632
- ID, name, group, category, level, skillpoints.
@@ -40,23 +46,13 @@ The app is aimed at capsuleers who want a quick overview of their training witho
4046

4147
- **Data export**
4248
- Export **All Skills**, **Filtered Skills**, or **Skill Queue** to:
43-
- CSV
44-
- JSON
45-
- XML
46-
- Text (human‑readable list)
47-
- Python (list of dicts)
48-
- Clipboard (copies data to the system clipboard)
49+
- CSV, JSON, XML, Text, Python, Clipboard.
4950
- `Append date` option — adds `_YYYYMMDD` to the file name.
5051

5152
- **UI**
52-
- Dark EVE‑style theme.
53-
- Left sidebar with characters and actions:
54-
- Add Character
55-
- Remove
56-
- Refresh Data
57-
- Quit
58-
- Buy me a coffee
59-
- Responsive layout that works well at common resolutions (1280×720 and above).
53+
- Premium Dark EVE-style theme.
54+
- Responsive layout with Sidebar navigation.
55+
- Automatic DPI scaling and better multi-monitor support.
6056

6157
***
6258

Screenshots/Screenshot1.png

445 KB
Loading

Screenshots/Screenshot2.png

398 KB
Loading

Screenshots/Screenshot_1.png

-42.5 KB
Binary file not shown.

Screenshots/Screenshot_2.png

-126 KB
Binary file not shown.

src/gui/app.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,15 +432,80 @@ def _open_skill_plan(self):
432432
SkillPlanManager(self, self.current_skills)
433433

434434
def _on_about_click(self):
435-
messagebox.showinfo("About",
436-
"Personal Skill Monitor v0.2.0\n\n"
437-
"Lightweight EVE Online skill management.\n\n"
438-
"Author: Fridman86\n"
439-
"GitHub: github.com/Fridman86/Personal-Skill-Monitor")
435+
top = tk.Toplevel(self)
436+
top.title("About")
437+
top.resizable(True, True)
438+
439+
# Apply theme colors roughly
440+
bg = "#1c2230"
441+
fg = "#e0e0e0"
442+
accent = "#3aa8d0"
443+
444+
top.configure(bg=bg)
445+
446+
# Main container with padding
447+
main_f = tk.Frame(top, bg=bg, padx=30, pady=25)
448+
main_f.pack(fill=tk.BOTH, expand=True)
449+
450+
# Header
451+
tk.Label(main_f, text="Personal Skill Monitor",
452+
font=("Segoe UI", 16, "bold"), fg=accent, bg=bg).pack(pady=(0, 5))
453+
454+
tk.Label(main_f, text="v0.2.1",
455+
font=("Segoe UI", 10), fg="#888888", bg=bg).pack(pady=(0, 20))
456+
457+
# Description
458+
desc = ("Lightweight EVE Online skill management tool.\n"
459+
"Track skills, view queue, and plan your training.")
460+
tk.Label(main_f, text=desc, font=("Segoe UI", 10),
461+
fg=fg, bg=bg, justify=tk.CENTER, wraplength=400).pack(pady=10)
462+
463+
# Links Frame
464+
links = tk.Frame(main_f, bg=bg)
465+
links.pack(pady=20)
466+
467+
# GitHub
468+
def open_github(e=None):
469+
webbrowser.open("https://github.com/Fridman86/Personal-Skill-Monitor")
470+
471+
lbl_git = tk.Label(links, text="GitHub Repository",
472+
font=("Segoe UI", 10, "underline"),
473+
fg=accent, bg=bg, cursor="hand2")
474+
lbl_git.pack(pady=5)
475+
lbl_git.bind("<Button-1>", open_github)
476+
477+
# Buy Me a Coffee
478+
def open_coffee(e=None):
479+
webbrowser.open("https://buymeacoffee.com/ifridman")
480+
481+
# Coffee container
482+
coffee_frame = tk.Frame(links, bg="#FFDD00", padx=15, pady=8, cursor="hand2")
483+
coffee_frame.pack(pady=(15, 0))
484+
485+
lbl_coffee = tk.Label(coffee_frame, text="☕ Buy me a coffee",
486+
font=("Cookie", 12, "bold") if "Cookie" in tk.font.families() else ("Segoe UI", 11, "bold"),
487+
fg="#000000", bg="#FFDD00", cursor="hand2")
488+
lbl_coffee.pack()
489+
490+
coffee_frame.bind("<Button-1>", open_coffee)
491+
lbl_coffee.bind("<Button-1>", open_coffee)
492+
493+
# Close button at the bottom
494+
btn_close = tk.Button(main_f, text="Close", command=top.destroy,
495+
bg="#2a3044", fg=fg, bd=0, padx=20, pady=5, cursor="hand2")
496+
btn_close.pack(pady=(30, 0))
497+
498+
# Center on screen
499+
top.update_idletasks()
500+
top.minsize(top.winfo_reqwidth(), top.winfo_reqheight())
501+
502+
w = top.winfo_reqwidth()
503+
h = top.winfo_reqheight()
504+
x = self.winfo_x() + (self.winfo_width() // 2) - (w // 2)
505+
y = self.winfo_y() + (self.winfo_height() // 2) - (h // 2)
506+
top.geometry(f"+{x}+{y}")
440507

441508
def _on_quit(self):
442509
if messagebox.askyesno("Exit", "Exit Personal Skill Monitor?"):
443510
self.destroy()
444511

445-
def _on_coffee_click(self):
446-
webbrowser.open("https://buymeacoffee.com/ifridman")

src/ui/tooltip.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,17 @@ def __init__(self, treeview, text_func,
9898
self._dwell_ms = 0
9999
self._alive = True
100100

101-
self._log("Initialized TreeviewTooltip")
101+
# Prevent multiple tooltips on same widget
102+
if hasattr(treeview, "_tooltip_ref"):
103+
try:
104+
old = treeview._tooltip_ref
105+
if old and old._alive:
106+
old._alive = False # Kill old instance
107+
except:
108+
pass
109+
treeview._tooltip_ref = self
110+
111+
self._log(f"Initialized TreeviewTooltip on {id(self.tree)}")
102112

103113
# Cleanup on widget destroy
104114
treeview.bind("<Destroy>", self._on_destroy, add="+")
@@ -110,7 +120,7 @@ def _log(self, msg):
110120
try:
111121
with open("/tmp/psm_debug.log", "a") as f:
112122
import datetime
113-
f.write(f"{datetime.datetime.now()} {msg}\n")
123+
f.write(f"{datetime.datetime.now()} [Tree:{id(self.tree)}] {msg}\n")
114124
except Exception:
115125
pass
116126

@@ -159,13 +169,11 @@ def _poll(self):
159169
self._current_row = row_id
160170
self._dwell_ms = 0
161171
else:
162-
# On heading or empty area
163172
if self._current_row is not None:
164173
self._hide()
165174
self._current_row = None
166175
self._dwell_ms = 0
167176
else:
168-
# Mouse not over the treeview
169177
if self._current_row is not None or self._tw is not None:
170178
self._hide()
171179
self._current_row = None
@@ -190,23 +198,28 @@ def _show_tooltip(self, row_id, mouse_x, mouse_y):
190198

191199
try:
192200
item = self.tree.item(row_id)
193-
except (tk.TclError, RuntimeError):
194-
return
201+
except (tk.TclError, RuntimeError) as e:
202+
self._log(f"Error getting item {row_id}: {e}")
203+
return
195204

196205
values = item.get("values", ())
197206
if not values:
198207
text = item.get("text", "")
199208
if text:
200209
values = (text,)
201210
else:
211+
self._log(f"No values for row {row_id}")
202212
return
203213

204214
try:
205215
result = self.text_func(values)
206-
except Exception:
216+
# self._log(f"text_func result: {result}") # Verbose but helpful
217+
except Exception as e:
218+
self._log(f"text_func failed: {e}")
207219
return
208220

209221
if not result:
222+
# self._log("text_func returned empty/None")
210223
return
211224

212225
if isinstance(result, tuple) and len(result) == 2:
@@ -221,7 +234,10 @@ def _show_tooltip(self, row_id, mouse_x, mouse_y):
221234
# Create tooltip window
222235
try:
223236
self._tw = tip = tk.Toplevel(self.tree)
237+
# Use transient instead of overrideredirect for testing?
238+
# tip.transient(self.tree)
224239
tip.wm_overrideredirect(True)
240+
225241
try:
226242
tip.attributes("-topmost", True)
227243
except Exception:
@@ -242,14 +258,14 @@ def _show_tooltip(self, row_id, mouse_x, mouse_y):
242258
foreground=self.title_fg, relief="flat",
243259
borderwidth=0, font=self.title_font,
244260
wraplength=self.WRAP_LENGTH,
245-
padx=10, pady=(6, 0), anchor="w").pack(fill=tk.X)
261+
padx=10, pady=5, anchor="w").pack(fill=tk.X)
246262

247263
tk.Label(inner, text=description,
248264
justify=tk.LEFT, background=self.bg,
249265
foreground=self.fg, relief="flat",
250266
borderwidth=0, font=self.font,
251267
wraplength=self.WRAP_LENGTH,
252-
padx=10, pady=(3 if title else 6, 6),
268+
padx=10, pady=5,
253269
anchor="w").pack(fill=tk.X)
254270

255271
# Position tooltip near cursor
@@ -267,7 +283,10 @@ def _show_tooltip(self, row_id, mouse_x, mouse_y):
267283
y = mouse_y - tip_h - 8
268284

269285
tip.wm_geometry(f"+{x}+{y}")
270-
except (tk.TclError, RuntimeError):
286+
self._log(f"Tooltip shown at +{x}+{y}")
287+
288+
except (tk.TclError, RuntimeError) as e:
289+
self._log(f"Tooltip creation failed: {e}")
271290
self._hide()
272291

273292
def _hide(self):

0 commit comments

Comments
 (0)