cs2tracker 2.1.13__py3-none-any.whl → 2.1.15__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.

Potentially problematic release.


This version of cs2tracker might be problematic. Click here for more details.

@@ -4,24 +4,25 @@ from shutil import copy
4
4
  from subprocess import PIPE, STDOUT
5
5
  from threading import Thread
6
6
  from tkinter import messagebox, ttk
7
- from urllib.parse import unquote
8
7
 
9
8
  from nodejs import node
10
9
  from ttk_text import ThemedText
11
10
 
11
+ from cs2tracker.config import get_config
12
12
  from cs2tracker.constants import (
13
13
  CONFIG_FILE,
14
14
  CONFIG_FILE_BACKUP,
15
+ DATA_DIR,
15
16
  INVENTORY_IMPORT_FILE,
16
17
  INVENTORY_IMPORT_SCRIPT,
17
18
  )
18
- from cs2tracker.util import get_config
19
+ from cs2tracker.util.tkinter import centered, size_info
19
20
 
20
21
  ADD_CUSTOM_ITEM_TITLE = "Add Custom Item"
21
- ADD_CUSTOM_ITEM_SIZE = "500x220"
22
+ ADD_CUSTOM_ITEM_SIZE = "500x230"
22
23
 
23
24
  IMPORT_INVENTORY_TITLE = "Import Steam Inventory"
24
- IMPORT_INVENTORY_SIZE = "500x450"
25
+ IMPORT_INVENTORY_SIZE = "700x350"
25
26
 
26
27
  IMPORT_INVENTORY_PROCESS_TITLE = "Importing Steam Inventory..."
27
28
  IMPORT_INVENTORY_PROCESS_SIZE = "700x500"
@@ -30,15 +31,18 @@ config = get_config()
30
31
 
31
32
 
32
33
  class ConfigEditorFrame(ttk.Frame):
33
- def __init__(self, parent):
34
+ def __init__(self, window):
34
35
  """Initialize the configuration editor frame that allows users to view and edit
35
36
  the configuration options.
36
37
  """
37
- super().__init__(parent, padding=15)
38
+ super().__init__(window, padding=15)
38
39
 
39
- self.parent = parent
40
+ self.window = window
41
+ self.edit_entry = None
40
42
  self._add_widgets()
41
43
 
44
+ self.tree.focus_set()
45
+
42
46
  def _add_widgets(self):
43
47
  """Configure the main editor frame which displays the configuration options in a
44
48
  structured way.
@@ -46,82 +50,176 @@ class ConfigEditorFrame(ttk.Frame):
46
50
  self._configure_treeview()
47
51
  self.tree.pack(expand=True, fill="both")
48
52
 
49
- button_frame = ConfigEditorButtonFrame(self, self.tree)
53
+ button_frame = ConfigEditorButtonFrame(self)
50
54
  button_frame.pack(side="bottom", padx=10, pady=(0, 10))
51
55
 
52
- def _set_cell_value(self, event):
56
+ def save_config(self):
57
+ """Save the current configuration from the treeview to the config file."""
58
+ config.delete_display_sections()
59
+ for section in self.tree.get_children():
60
+ config.add_section(section)
61
+ for item in self.tree.get_children(section):
62
+ item_name = self.tree.item(item, "text")
63
+ if section not in ("App Settings", "User Settings"):
64
+ config_option = config.name_to_option(item_name, href=True)
65
+ else:
66
+ config_option = config.name_to_option(item_name)
67
+ value = self.tree.item(item, "values")[0]
68
+ config.set(section, config_option, value)
69
+
70
+ config.write_to_file()
71
+ if not config.valid:
72
+ config.load_from_file()
73
+ self.reload_config_into_tree()
74
+ messagebox.showerror(
75
+ "Config Error",
76
+ f"The configuration is invalid. ({config.last_error})",
77
+ parent=self.window,
78
+ )
79
+ self.window.focus_set()
80
+ self.tree.focus_set()
81
+
82
+ def _save_edit(self, event, row, column):
83
+ """Save the edited value in the treeview and destroy the entry widget."""
84
+ self.tree.set(row, column=column, value=event.widget.get())
85
+ self.save_config()
86
+ event.widget.destroy()
87
+
88
+ def _set_cell_value(self, event, row=None, column=None):
53
89
  """
54
- Set the value of a cell in the treeview to be editable when double- clicked.
90
+ Set the value of a cell in the treeview to be editable when double-clicked.
55
91
 
56
92
  Source: https://stackoverflow.com/questions/75787251/create-an-editable-tkinter-treeview-with-keyword-connection
57
93
  """
94
+ try:
95
+ if not row or not column:
96
+ row = self.tree.identify_row(event.y)
97
+ column = self.tree.identify_column(event.x)
58
98
 
59
- def save_edit(event):
60
- self.tree.set(row, column=column, value=event.widget.get())
61
- event.widget.destroy()
99
+ item_text = self.tree.item(row, "text")
100
+ if column == "#0" or any(item_text == section for section in config.sections()):
101
+ return
102
+ item_value = self.tree.item(row, "values")[0]
62
103
 
63
- try:
64
- row = self.tree.identify_row(event.y)
65
- column = self.tree.identify_column(event.x)
66
- item_text = self.tree.set(row, column)
67
- if item_text.strip() == "":
68
- left_item_text = self.tree.item(row, "text")
69
- # Don't allow editing of section headers
70
- if any(left_item_text == section for section in config.sections()):
71
- return
72
104
  x, y, w, h = self.tree.bbox(row, column)
73
- entryedit = ttk.Entry(self)
74
- entryedit.place(x=x, y=y, width=w, height=h + 3) # type: ignore
75
- entryedit.insert("end", item_text)
76
- entryedit.bind("<Return>", save_edit)
77
- entryedit.focus_set()
78
- entryedit.grab_set()
105
+ self.edit_entry = ttk.Entry(self, justify="center", font=("Helvetica", 11))
106
+ self.edit_entry.place(x=x, y=y, width=w, height=h + 3) # type: ignore
107
+ self.edit_entry.insert("end", item_value)
108
+ self.edit_entry.bind("<Return>", lambda e: self._save_edit(e, row, column))
109
+ self.edit_entry.focus_set()
110
+ self.edit_entry.grab_set()
79
111
  except Exception:
80
- pass
112
+ return
113
+
114
+ def _set_selection_value(self, _):
115
+ """Set the value of the currently selected cell in the treeview to be
116
+ editable.
117
+ """
118
+ selected = self.tree.selection()
119
+ if selected:
120
+ row = selected[0]
121
+ column = "#1"
122
+ self._set_cell_value(None, row=row, column=column)
81
123
 
82
- def _destroy_entries(self, _):
124
+ def _delete_selection_value(self, _):
125
+ """
126
+ Delete the value of the currently selected cell in the treeview.
127
+
128
+ This only works for custom items, as other sections are not editable.
129
+ """
130
+ selected = self.tree.selection()
131
+ if selected:
132
+ item = selected[0]
133
+ section_name = self.tree.parent(item)
134
+ if section_name in ("Stickers", "Skins"):
135
+ next_option = self.tree.next(item)
136
+ self.tree.delete(item)
137
+ self.save_config()
138
+ if next_option:
139
+ self.tree.focus(next_option)
140
+ self.tree.selection_set(next_option)
141
+ else:
142
+ self.tree.focus(section_name)
143
+ self.tree.selection_set(section_name)
144
+
145
+ def _destroy_entry(self, _):
83
146
  """Destroy any entry widgets in the treeview on an event, such as a mouse wheel
84
147
  movement.
85
148
  """
86
- for widget in self.winfo_children():
87
- if isinstance(widget, ttk.Entry):
88
- widget.destroy()
89
-
90
- def _destroy_entry(self, event):
91
- """Destroy the entry widget on an even targeting it."""
92
- if isinstance(event.widget, ttk.Entry):
93
- event.widget.destroy()
149
+ if self.edit_entry:
150
+ self.edit_entry.destroy()
151
+ self.edit_entry = None
152
+ self.tree.focus_set()
94
153
 
95
154
  def _make_tree_editable(self):
96
155
  """Add a binding to the treeview that allows double-clicking on a cell to edit
97
156
  its value.
98
157
  """
99
158
  self.tree.bind("<Double-1>", self._set_cell_value)
100
- self.parent.bind("<MouseWheel>", self._destroy_entries) # type: ignore
101
- self.parent.bind("<Button-1>", self._destroy_entry) # type: ignore
159
+ self.tree.bind("<Return>", self._set_selection_value)
160
+ self.tree.bind("<BackSpace>", self._delete_selection_value)
161
+ self.window.bind("<MouseWheel>", self._destroy_entry)
162
+ self.window.bind("<Escape>", self._destroy_entry)
102
163
 
103
164
  def _load_config_into_tree(self):
104
165
  """Load the configuration options into the treeview for display and editing."""
105
166
  for section in config.sections():
167
+ # App Settings are internal and shouldn't be displayed to the user
106
168
  if section == "App Settings":
107
169
  continue
170
+
108
171
  section_level = self.tree.insert("", "end", iid=section, text=section)
109
- for config_option, value in config.items(section):
110
- if section == "Custom Items":
111
- custom_item_name = unquote(config_option.split("/")[-1])
112
- self.tree.insert(section_level, "end", text=custom_item_name, values=[value])
172
+
173
+ # Items in the Stickers, Cases, and Skins sections should be displayed alphabetically sorted
174
+ section_items = config.items(section)
175
+ if section in ("Stickers", "Cases", "Skins"):
176
+ section_items = sorted(section_items)
177
+
178
+ for config_option, value in section_items:
179
+ if section not in ("User Settings", "App Settings"):
180
+ option_name = config.option_to_name(config_option, href=True)
181
+ self.tree.insert(
182
+ section_level,
183
+ "end",
184
+ iid=f"{section}-{option_name}",
185
+ text=option_name,
186
+ values=[value],
187
+ )
113
188
  else:
114
- option_name = config_option.replace("_", " ").title()
115
- self.tree.insert(section_level, "end", text=option_name, values=[value])
189
+ option_name = config.option_to_name(config_option)
190
+ self.tree.insert(
191
+ section_level,
192
+ "end",
193
+ iid=f"{section}-{option_name}",
194
+ text=option_name,
195
+ values=[value],
196
+ )
197
+
198
+ self.tree.focus("User Settings")
199
+ self.tree.selection_set("User Settings")
116
200
 
117
201
  def reload_config_into_tree(self):
118
- """Reload the configuration options into the treeview for display and
119
- editing.
202
+ """Reload the configuration options into the treeview for display and editing
203
+ and maintain the users current selection.
120
204
  """
205
+ selected = self.tree.selection()
206
+ selected_text, selected_section = None, None
207
+ if selected:
208
+ selected_text = self.tree.item(selected[0], "text")
209
+ selected_section = self.tree.parent(selected[0])
210
+
121
211
  for item in self.tree.get_children():
122
212
  self.tree.delete(item)
123
213
  self._load_config_into_tree()
124
214
 
215
+ if selected_section:
216
+ self.tree.item(selected_section, open=True)
217
+ self.tree.focus(f"{selected_section}-{selected_text}")
218
+ self.tree.selection_set(f"{selected_section}-{selected_text}")
219
+ elif selected:
220
+ self.tree.focus(selected_text)
221
+ self.tree.selection_set(selected_text) # type: ignore
222
+
125
223
  def _configure_treeview(self):
126
224
  """Configure a treeview to display and edit configuration options."""
127
225
  scrollbar = ttk.Scrollbar(self)
@@ -146,14 +244,13 @@ class ConfigEditorFrame(ttk.Frame):
146
244
 
147
245
 
148
246
  class ConfigEditorButtonFrame(ttk.Frame):
149
- def __init__(self, parent, tree):
247
+ def __init__(self, editor_frame):
150
248
  """Initialize the button frame that contains buttons for saving the updated
151
249
  configuration and adding custom items.
152
250
  """
153
- super().__init__(parent, padding=10)
251
+ super().__init__(editor_frame, padding=10)
154
252
 
155
- self.parent = parent
156
- self.tree = tree
253
+ self.editor_frame = editor_frame
157
254
  self.custom_item_dialog = None
158
255
 
159
256
  self._add_widgets()
@@ -162,13 +259,10 @@ class ConfigEditorButtonFrame(ttk.Frame):
162
259
  """Add buttons to the button frame for saving the configuration and adding
163
260
  custom items.
164
261
  """
165
- save_button = ttk.Button(self, text="Save", command=self._save_config)
166
- save_button.pack(side="left", expand=True, padx=5)
167
-
168
262
  reset_button = ttk.Button(self, text="Reset", command=self._reset_config)
169
263
  reset_button.pack(side="left", expand=True, padx=5)
170
264
 
171
- custom_item_button = ttk.Button(self, text="Add Custom Item", command=self._add_custom_item)
265
+ custom_item_button = ttk.Button(self, text="Add Item", command=self._add_custom_item)
172
266
  custom_item_button.pack(side="left", expand=True, padx=5)
173
267
 
174
268
  import_inventory_button = ttk.Button(
@@ -176,72 +270,66 @@ class ConfigEditorButtonFrame(ttk.Frame):
176
270
  )
177
271
  import_inventory_button.pack(side="left", expand=True, padx=5)
178
272
 
179
- def _save_config(self):
180
- """Save the current configuration from the treeview to the config file."""
181
- for child in self.tree.get_children():
182
- for item in self.tree.get_children(child):
183
- title_option = self.tree.item(item, "text")
184
- config_option = title_option.lower().replace(" ", "_")
185
- value = self.tree.item(item, "values")[0]
186
- section = self.tree.parent(item)
187
- section_name = self.tree.item(section, "text")
188
- if section_name == "Custom Items":
189
- # custom items are already saved upon creation (Saving them again would result in duplicates)
190
- continue
191
- config.set(section_name, config_option, value)
192
-
193
- config.write_to_file()
194
- if config.valid:
195
- messagebox.showinfo("Config Saved", "The configuration has been saved successfully.")
196
- else:
197
- config.load()
198
- self.parent.reload_config_into_tree()
199
- messagebox.showerror(
200
- "Config Error",
201
- f"The configuration is invalid. ({config.last_error})",
202
- )
203
-
204
273
  def _reset_config(self):
205
274
  """Reset the configuration file to its default state."""
206
275
  confirm = messagebox.askokcancel(
207
- "Reset Config", "Are you sure you want to reset the configuration?"
276
+ "Reset Config",
277
+ "Are you sure you want to reset the configuration?",
278
+ parent=self.editor_frame,
208
279
  )
209
280
  if confirm:
210
281
  copy(CONFIG_FILE_BACKUP, CONFIG_FILE)
211
- config.load()
212
- self.parent.reload_config_into_tree()
282
+ config.load_from_file()
283
+ self.editor_frame.reload_config_into_tree()
284
+ self.editor_frame.focus_set()
285
+ self.editor_frame.tree.focus_set()
213
286
 
214
287
  def _add_custom_item(self):
215
288
  """Open a window to add a new custom item."""
216
- custom_item_window = tk.Toplevel(self.parent)
289
+ custom_item_window = tk.Toplevel(self.editor_frame)
217
290
  custom_item_window.title(ADD_CUSTOM_ITEM_TITLE)
218
- custom_item_window.geometry(ADD_CUSTOM_ITEM_SIZE)
291
+ custom_item_window.geometry(centered(custom_item_window, ADD_CUSTOM_ITEM_SIZE))
292
+ custom_item_window.minsize(*size_info(ADD_CUSTOM_ITEM_SIZE))
293
+ custom_item_window.focus_set()
219
294
 
220
- custom_item_frame = CustomItemFrame(custom_item_window, self.parent, self.tree)
295
+ def on_close():
296
+ custom_item_window.destroy()
297
+ self.editor_frame.tree.focus_set()
298
+
299
+ custom_item_window.protocol("WM_DELETE_WINDOW", on_close)
300
+
301
+ custom_item_frame = CustomItemFrame(custom_item_window, self.editor_frame)
221
302
  custom_item_frame.pack(expand=True, fill="both", padx=15, pady=15)
222
303
 
223
304
  def _import_steam_inventory(self):
224
305
  """Open a window to import the user's Steam inventory."""
225
- steam_inventory_window = tk.Toplevel(self.parent)
306
+ steam_inventory_window = tk.Toplevel(self.editor_frame)
226
307
  steam_inventory_window.title(IMPORT_INVENTORY_TITLE)
227
- steam_inventory_window.geometry(IMPORT_INVENTORY_SIZE)
308
+ steam_inventory_window.geometry(centered(steam_inventory_window, IMPORT_INVENTORY_SIZE))
309
+ steam_inventory_window.minsize(*size_info(IMPORT_INVENTORY_SIZE))
310
+ steam_inventory_window.focus_set()
311
+
312
+ def on_close():
313
+ steam_inventory_window.destroy()
314
+ self.editor_frame.tree.focus_set()
228
315
 
229
- steam_inventory_frame = InventoryImportFrame(steam_inventory_window, self)
316
+ steam_inventory_window.protocol("WM_DELETE_WINDOW", on_close)
317
+
318
+ steam_inventory_frame = InventoryImportFrame(steam_inventory_window, self.editor_frame)
230
319
  steam_inventory_frame.pack(expand=True, fill="both", padx=15, pady=15)
231
320
 
232
321
 
233
322
  class CustomItemFrame(ttk.Frame):
234
- def __init__(self, parent, grandparent, tree):
323
+ def __init__(self, window, editor_frame):
235
324
  """Initialize the custom item frame that allows users to add custom items."""
236
- super().__init__(parent, style="Card.TFrame", padding=15)
237
- self.parent = parent
238
- self.grandparent = grandparent
239
- self.tree = tree
325
+ super().__init__(window, style="Card.TFrame", padding=15)
326
+ self.window = window
327
+ self.editor_frame = editor_frame
240
328
  self._add_widgets()
241
329
 
242
330
  def _add_widgets(self):
243
331
  """Add widgets to the custom item frame for entering item details."""
244
- ttk.Label(self, text="Item URL:").pack(pady=5)
332
+ ttk.Label(self, text="Steam Market Listing URL:").pack(pady=5)
245
333
  item_url_entry = ttk.Entry(self)
246
334
  item_url_entry.pack(fill="x", padx=10)
247
335
 
@@ -255,92 +343,155 @@ class CustomItemFrame(ttk.Frame):
255
343
  command=lambda: self._add_custom_item(item_url_entry.get(), item_owned_entry.get()),
256
344
  )
257
345
  add_button.pack(pady=10)
346
+ self.window.bind("<Return>", lambda _: add_button.invoke())
347
+
348
+ def _update_existing(self, section, item_name, item_owned):
349
+ """
350
+ Try to update an item in the treeview with the new item details.
258
351
 
259
- def _add_custom_item(self, item_url, item_owned):
352
+ :return: True if the item was updated, False if it was not found.
353
+ """
354
+ for existing_item in self.editor_frame.tree.get_children(section):
355
+ existing_item_name = self.editor_frame.tree.item(existing_item, "text")
356
+ if item_name == existing_item_name:
357
+ self.editor_frame.tree.set(existing_item, column="#1", value=item_owned)
358
+ self.editor_frame.focus_set()
359
+ self.editor_frame.save_config()
360
+ self.window.destroy()
361
+ return True
362
+ return False
363
+
364
+ def _get_insert_index(self, item_name, section):
365
+ """Get the index to insert the new item in alphabetical order."""
366
+ insert_index = "end"
367
+ for existing_item_index, existing_item in enumerate(
368
+ self.editor_frame.tree.get_children(section)
369
+ ):
370
+ existing_item_name = self.editor_frame.tree.item(existing_item, "text")
371
+ if item_name < existing_item_name:
372
+ insert_index = existing_item_index
373
+ break
374
+ return insert_index
375
+
376
+ def _add_custom_item(self, item_href, item_owned):
260
377
  """Add a custom item to the configuration."""
261
- if not item_url or not item_owned:
262
- messagebox.showerror("Input Error", "All fields must be filled out.")
378
+ if not item_href or not item_owned:
379
+ messagebox.showerror(
380
+ "Input Error", "All fields must be filled out.", parent=self.window
381
+ )
382
+ return
383
+ if config.option_exists(item_href, exclude_sections=("Stickers", "Skins")):
384
+ messagebox.showerror(
385
+ "Item Exists", "This item already exists in another section.", parent=self.window
386
+ )
263
387
  return
388
+
264
389
  try:
265
- if int(item_owned) < 0:
266
- raise ValueError("Owned count must be a non-negative integer.")
390
+ item_name = config.option_to_name(item_href, href=True)
267
391
  except ValueError as error:
268
- messagebox.showerror("Input Error", f"Invalid owned count: {error}")
392
+ messagebox.showerror("Invalid URL", str(error), parent=self.window)
269
393
  return
270
394
 
271
- config.set("Custom Items", item_url, item_owned)
272
- config.write_to_file()
273
- if config.valid:
274
- config.load()
275
- self.grandparent.reload_config_into_tree()
276
- self.parent.destroy()
277
- else:
278
- config.remove_option("Custom Items", item_url)
279
- messagebox.showerror(
280
- "Config Error",
281
- f"The configuration is invalid. ({config.last_error})",
282
- )
395
+ if self._update_existing("Stickers", item_name, item_owned):
396
+ return
397
+ if self._update_existing("Skins", item_name, item_owned):
398
+ return
399
+
400
+ section = "Stickers" if item_name.startswith("Sticker") else "Skins"
401
+ insert_index = self._get_insert_index(item_name, section)
402
+ self.editor_frame.tree.insert(
403
+ section,
404
+ insert_index,
405
+ iid=f"{section}-{item_name}",
406
+ text=item_name,
407
+ values=[item_owned],
408
+ )
409
+ self.editor_frame.save_config()
410
+ self.window.destroy()
283
411
 
284
412
 
285
413
  class InventoryImportFrame(ttk.Frame):
286
414
  # pylint: disable=too-many-instance-attributes
287
- def __init__(self, parent, grandparent):
415
+ def __init__(self, window, editor_frame):
288
416
  """Initialize the inventory import frame that allows users to import their Steam
289
417
  inventory.
290
418
  """
291
- super().__init__(parent, style="Card.TFrame", padding=10)
292
- self.parent = parent
293
- self.grandparent = grandparent
419
+ super().__init__(window, padding=10)
420
+ self.window = window
421
+ self.editor_frame = editor_frame
294
422
  self._add_widgets()
295
423
 
296
424
  def _add_widgets(self):
297
425
  """Add widgets to the inventory import frame."""
426
+ self._configure_entries()
427
+ self.user_name_label.pack(pady=5)
428
+ self.user_name_entry.pack(fill="x", expand=True, padx=10)
429
+ self.password_label.pack(pady=5)
430
+ self.password_entry.pack(fill="x", expand=True, padx=10)
431
+ self.two_factor_label.pack(pady=5)
432
+ self.two_factor_entry.pack(fill="x", expand=True, padx=10)
433
+ self.import_button.pack(pady=10)
434
+ self.entry_frame.pack(side="left", padx=10, pady=(0, 20), fill="both", expand=True)
435
+
298
436
  self._configure_checkboxes()
437
+ self.storage_units_checkbox.pack(anchor="w", padx=10, pady=5)
438
+ self.regular_inventory_checkbox.pack(anchor="w", padx=10, pady=5)
299
439
  self.import_cases_checkbox.pack(anchor="w", padx=10, pady=5)
300
440
  self.import_sticker_capsules_checkbox.pack(anchor="w", padx=10, pady=5)
301
441
  self.import_stickers_checkbox.pack(anchor="w", padx=10, pady=5)
302
442
  self.import_others_checkbox.pack(anchor="w", padx=10, pady=5)
303
-
304
- self._configure_entries()
305
- self.user_name_label.pack(pady=10)
306
- self.user_name_entry.pack(fill="x", padx=50)
307
- self.password_label.pack(pady=10)
308
- self.password_entry.pack(fill="x", padx=50)
309
- self.two_factor_label.pack(pady=10)
310
- self.two_factor_entry.pack(fill="x", padx=50)
311
-
312
- self.import_button = ttk.Button(self, text="Import", command=self._import_inventory)
313
- self.import_button.pack(pady=10)
443
+ self.checkbox_frame.pack(side="left", padx=10, pady=(0, 20), fill="both", expand=True)
314
444
 
315
445
  def _configure_checkboxes(self):
316
446
  # pylint: disable=attribute-defined-outside-init
317
447
  """Configure the checkboxes for selecting what to import from the Steam
318
448
  inventory.
319
449
  """
450
+ self.checkbox_frame = ttk.LabelFrame(self, text="Import Settings", padding=15)
451
+
452
+ self.regular_inventory_value = tk.BooleanVar(value=False)
453
+ self.regular_inventory_checkbox = ttk.Checkbutton(
454
+ self.checkbox_frame,
455
+ text="Regular Inventory",
456
+ variable=self.regular_inventory_value,
457
+ style="Switch.TCheckbutton",
458
+ )
459
+
460
+ self.storage_units_value = tk.BooleanVar(value=True)
461
+ self.storage_units_checkbox = ttk.Checkbutton(
462
+ self.checkbox_frame,
463
+ text="Storage Units",
464
+ variable=self.storage_units_value,
465
+ style="Switch.TCheckbutton",
466
+ )
467
+
320
468
  self.import_cases_value = tk.BooleanVar(value=True)
321
469
  self.import_cases_checkbox = ttk.Checkbutton(
322
- self, text="Import Cases", variable=self.import_cases_value, style="Switch.TCheckbutton"
470
+ self.checkbox_frame,
471
+ text="Import Cases",
472
+ variable=self.import_cases_value,
473
+ style="Switch.TCheckbutton",
323
474
  )
324
475
 
325
476
  self.import_sticker_capsules_value = tk.BooleanVar(value=True)
326
477
  self.import_sticker_capsules_checkbox = ttk.Checkbutton(
327
- self,
478
+ self.checkbox_frame,
328
479
  text="Import Sticker Capsules",
329
480
  variable=self.import_sticker_capsules_value,
330
481
  style="Switch.TCheckbutton",
331
482
  )
332
483
 
333
- self.import_stickers_value = tk.BooleanVar(value=True)
484
+ self.import_stickers_value = tk.BooleanVar(value=False)
334
485
  self.import_stickers_checkbox = ttk.Checkbutton(
335
- self,
486
+ self.checkbox_frame,
336
487
  text="Import Stickers",
337
488
  variable=self.import_stickers_value,
338
489
  style="Switch.TCheckbutton",
339
490
  )
340
491
 
341
- self.import_others_value = tk.BooleanVar(value=True)
492
+ self.import_others_value = tk.BooleanVar(value=False)
342
493
  self.import_others_checkbox = ttk.Checkbutton(
343
- self,
494
+ self.checkbox_frame,
344
495
  text="Import Other Items",
345
496
  variable=self.import_others_value,
346
497
  style="Switch.TCheckbutton",
@@ -351,14 +502,37 @@ class InventoryImportFrame(ttk.Frame):
351
502
  """Configure the entry fields for Steam username, password, and two-factor
352
503
  code.
353
504
  """
354
- self.user_name_label = ttk.Label(self, text="Steam Username:")
355
- self.user_name_entry = ttk.Entry(self)
505
+ self.entry_frame = ttk.Frame(self, style="Card.TFrame", padding=15)
506
+
507
+ self.user_name_label = ttk.Label(self.entry_frame, text="Steam Username:")
508
+ self.user_name_entry = ttk.Entry(self.entry_frame, justify="center", font=("Helvetica", 11))
356
509
 
357
- self.password_label = ttk.Label(self, text="Steam Password:")
358
- self.password_entry = ttk.Entry(self, show="*")
510
+ self.password_label = ttk.Label(self.entry_frame, text="Steam Password:")
511
+ self.password_entry = ttk.Entry(
512
+ self.entry_frame, show="*", justify="center", font=("Helvetica", 11)
513
+ )
359
514
 
360
- self.two_factor_label = ttk.Label(self, text="Steam Guard Code (if enabled):")
361
- self.two_factor_entry = ttk.Entry(self)
515
+ self.two_factor_label = ttk.Label(self.entry_frame, text="Steam Guard Code:")
516
+ self.two_factor_entry = ttk.Entry(
517
+ self.entry_frame, justify="center", font=("Helvetica", 11)
518
+ )
519
+
520
+ self.import_button = ttk.Button(
521
+ self.entry_frame, text="Import", command=self._import_inventory, state="disabled"
522
+ )
523
+
524
+ def check_form(_):
525
+ if (
526
+ len(self.user_name_entry.get().strip()) > 0
527
+ and len(self.password_entry.get().strip()) > 0
528
+ and len(self.two_factor_entry.get().strip()) > 0
529
+ ):
530
+ self.import_button.configure(state="normal")
531
+ else:
532
+ self.import_button.configure(state="disabled")
533
+
534
+ self.window.bind("<KeyRelease>", check_form)
535
+ self.window.bind("<Return>", lambda _: self.import_button.invoke())
362
536
 
363
537
  def _import_inventory(self):
364
538
  """
@@ -367,6 +541,9 @@ class InventoryImportFrame(ttk.Frame):
367
541
  This will also install the necessary npm packages if they are not already
368
542
  installed.
369
543
  """
544
+ regular_inventory = self.regular_inventory_value.get()
545
+ storage_units = self.storage_units_value.get()
546
+
370
547
  import_cases = self.import_cases_value.get()
371
548
  import_sticker_capsules = self.import_sticker_capsules_value.get()
372
549
  import_stickers = self.import_stickers_value.get()
@@ -380,6 +557,8 @@ class InventoryImportFrame(ttk.Frame):
380
557
  [
381
558
  INVENTORY_IMPORT_SCRIPT,
382
559
  INVENTORY_IMPORT_FILE,
560
+ str(regular_inventory),
561
+ str(storage_units),
383
562
  str(import_cases),
384
563
  str(import_sticker_capsules),
385
564
  str(import_stickers),
@@ -390,26 +569,37 @@ class InventoryImportFrame(ttk.Frame):
390
569
  ]
391
570
  )
392
571
 
393
- self.parent.destroy()
572
+ self.window.destroy()
394
573
 
395
574
  def _display_node_subprocess(self, node_cmd):
396
- text_window = tk.Toplevel(self.grandparent)
397
- text_window.title(IMPORT_INVENTORY_PROCESS_TITLE)
398
- text_window.geometry(IMPORT_INVENTORY_PROCESS_SIZE)
575
+ console_window = tk.Toplevel(self.editor_frame)
576
+ console_window.title(IMPORT_INVENTORY_PROCESS_TITLE)
577
+ console_window.geometry(centered(console_window, IMPORT_INVENTORY_PROCESS_SIZE))
578
+ console_window.minsize(*size_info(IMPORT_INVENTORY_PROCESS_SIZE))
579
+ console_window.focus_force()
580
+
581
+ def on_close():
582
+ console_window.destroy()
583
+ config.read_from_inventory_file()
584
+ self.editor_frame.reload_config_into_tree()
585
+ self.editor_frame.tree.focus_set()
586
+
587
+ console_window.protocol("WM_DELETE_WINDOW", on_close)
399
588
 
400
- process_frame = InventoryImportProcessFrame(text_window)
589
+ process_frame = InventoryImportProcessFrame(console_window, self.editor_frame)
401
590
  process_frame.pack(expand=True, fill="both", padx=15, pady=15)
591
+ process_frame.console.focus_force()
402
592
  process_frame.start(node_cmd)
403
- process_frame.console.focus_set()
404
593
 
405
594
 
406
595
  class InventoryImportProcessFrame(ttk.Frame):
407
596
  # pylint: disable=attribute-defined-outside-init
408
597
  # Source: https://stackoverflow.com/questions/27327886/issues-intercepting-subprocess-output-in-real-time
409
- def __init__(self, parent):
598
+ def __init__(self, window, editor_frame):
410
599
  """Initialize the frame that displays the output of the subprocess."""
411
- super().__init__(parent)
412
- self.parent = parent
600
+ super().__init__(window)
601
+ self.window = window
602
+ self.editor_frame = editor_frame
413
603
  self._add_widgets()
414
604
 
415
605
  def _add_widgets(self):
@@ -419,6 +609,7 @@ class InventoryImportProcessFrame(ttk.Frame):
419
609
 
420
610
  self.console = ThemedText(self, wrap="word", yscrollcommand=self.scrollbar.set)
421
611
  self.console.config(state="disabled")
612
+ self.console.tag_configure("error", foreground="red")
422
613
  self.console.pack(expand=True, fill="both", padx=10, pady=10)
423
614
 
424
615
  self.scrollbar.config(command=self.console.yview)
@@ -438,28 +629,31 @@ class InventoryImportProcessFrame(ttk.Frame):
438
629
  stdin=PIPE,
439
630
  stderr=STDOUT,
440
631
  text=True,
441
- bufsize=1,
442
632
  encoding="utf-8",
633
+ shell=True,
634
+ cwd=DATA_DIR,
443
635
  )
444
636
  self.queue = Queue()
445
637
  self.thread = Thread(target=self._read_lines, args=(self.process, self.queue), daemon=True)
446
638
  self.thread.start()
447
-
448
- self.after(100, self._update_lines)
639
+ self._update_lines()
449
640
 
450
641
  def _update_lines(self):
451
642
  """Update the text widget with lines from the subprocess output."""
452
643
  try:
453
644
  line = self.queue.get(block=False)
454
645
  self.console.config(state="normal")
455
- self.console.insert("end", line)
646
+ if "[ERROR]" in line:
647
+ self.console.insert("end", line, "error")
648
+ else:
649
+ self.console.insert("end", line)
456
650
  self.console.config(state="disabled")
457
651
  self.console.yview("end")
458
652
  except Empty:
459
653
  pass
460
654
 
461
655
  if self.process.poll() is None or not self.queue.empty():
462
- self.after(100, self._update_lines)
656
+ self.after(35, self._update_lines)
463
657
  else:
464
658
  self._cleanup()
465
659
 
@@ -467,8 +661,10 @@ class InventoryImportProcessFrame(ttk.Frame):
467
661
  """Clean up the process and thread after completion and trigger a config update
468
662
  from the newly written inventory file.
469
663
  """
470
- config.read_from_inventory_file()
471
- self.parent.master.master.reload_config_into_tree()
472
-
473
664
  self.process.wait()
474
665
  self.thread.join()
666
+
667
+ config.read_from_inventory_file()
668
+ self.editor_frame.reload_config_into_tree()
669
+ self.editor_frame.tree.focus_set()
670
+ self.window.destroy()