pytribeam 0.0.1__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 (57) hide show
  1. pytribeam/GUI/CustomTkinterWidgets/__init__.py +28 -0
  2. pytribeam/GUI/CustomTkinterWidgets/button.py +312 -0
  3. pytribeam/GUI/CustomTkinterWidgets/config.py +20 -0
  4. pytribeam/GUI/CustomTkinterWidgets/datepicker.py +209 -0
  5. pytribeam/GUI/CustomTkinterWidgets/dialog.py +108 -0
  6. pytribeam/GUI/CustomTkinterWidgets/entry.py +182 -0
  7. pytribeam/GUI/CustomTkinterWidgets/frame.py +383 -0
  8. pytribeam/GUI/CustomTkinterWidgets/images.py +17 -0
  9. pytribeam/GUI/CustomTkinterWidgets/label.py +71 -0
  10. pytribeam/GUI/CustomTkinterWidgets/listbox.py +109 -0
  11. pytribeam/GUI/CustomTkinterWidgets/menu.py +113 -0
  12. pytribeam/GUI/CustomTkinterWidgets/menubutton.py +204 -0
  13. pytribeam/GUI/CustomTkinterWidgets/progressbar.py +560 -0
  14. pytribeam/GUI/CustomTkinterWidgets/scrollbar.py +228 -0
  15. pytribeam/GUI/CustomTkinterWidgets/text.py +192 -0
  16. pytribeam/GUI/CustomTkinterWidgets/theme.py +153 -0
  17. pytribeam/GUI/CustomTkinterWidgets/tooltip.py +200 -0
  18. pytribeam/GUI/CustomTkinterWidgets/utils.py +668 -0
  19. pytribeam/GUI/__init__.py +1 -0
  20. pytribeam/GUI/common/__init__.py +25 -0
  21. pytribeam/GUI/common/config_manager.py +172 -0
  22. pytribeam/GUI/common/errors.py +88 -0
  23. pytribeam/GUI/common/logging_config.py +128 -0
  24. pytribeam/GUI/common/resources.py +118 -0
  25. pytribeam/GUI/common/threading_utils.py +472 -0
  26. pytribeam/GUI/config_ui/App.py +1316 -0
  27. pytribeam/GUI/config_ui/__init__.py +1 -0
  28. pytribeam/GUI/config_ui/editor_controller.py +441 -0
  29. pytribeam/GUI/config_ui/lookup.py +1317 -0
  30. pytribeam/GUI/config_ui/microscope_interface.py +264 -0
  31. pytribeam/GUI/config_ui/parameter_tracker.py +278 -0
  32. pytribeam/GUI/config_ui/pipeline_model.py +755 -0
  33. pytribeam/GUI/config_ui/validator.py +348 -0
  34. pytribeam/GUI/runner.py +631 -0
  35. pytribeam/GUI/runner_util/__init__.py +5 -0
  36. pytribeam/GUI/runner_util/experiment_controller.py +521 -0
  37. pytribeam/GUI/runner_util/ui_components.py +540 -0
  38. pytribeam/__init__.py +19 -0
  39. pytribeam/command_line.py +153 -0
  40. pytribeam/constants.py +390 -0
  41. pytribeam/factory.py +3578 -0
  42. pytribeam/fib.py +534 -0
  43. pytribeam/image.py +1671 -0
  44. pytribeam/insertable_devices.py +819 -0
  45. pytribeam/laser.py +998 -0
  46. pytribeam/log.py +415 -0
  47. pytribeam/stage.py +960 -0
  48. pytribeam/types.py +2353 -0
  49. pytribeam/utilities.py +1043 -0
  50. pytribeam/workflow.py +837 -0
  51. pytribeam-0.0.1.dist-info/LICENSE +23 -0
  52. pytribeam-0.0.1.dist-info/METADATA +37 -0
  53. pytribeam-0.0.1.dist-info/NOTICE.md +917 -0
  54. pytribeam-0.0.1.dist-info/RECORD +57 -0
  55. pytribeam-0.0.1.dist-info/WHEEL +5 -0
  56. pytribeam-0.0.1.dist-info/entry_points.txt +5 -0
  57. pytribeam-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Code taken from https://github.com/Aboghazala/AwesomeTkinter/
4
+ """
5
+
6
+ import tkinter as tk
7
+ from tkinter import ttk
8
+
9
+ from .button import Button, Button3d, Radiobutton, Checkbutton
10
+ from .frame import Frame3d, ScrollableFrame, ToggledFrame, ExpandableFrame
11
+ from .menu import RightClickMenu
12
+ from .progressbar import RadialProgressbar, RadialProgressbar3d, Segmentbar, Progressbar
13
+ from .scrollbar import SimpleScrollbar
14
+ from .text import ScrolledText
15
+ from .utils import *
16
+ from .config import *
17
+ from .theme import *
18
+ from .tooltip import tooltip
19
+ from .listbox import Listbox, DragDropListbox
20
+ from .menubutton import MenuButton, EntryMenuButton
21
+ from .entry import Entry, PathEntry
22
+ from .label import AutofitLabel, AutoWrappingLabel
23
+ from .dialog import filechooser, folderchooser
24
+ from .datepicker import DatePicker
25
+
26
+
27
+ def main():
28
+ print("Hello from CustomTkinterWidgets!")
@@ -0,0 +1,312 @@
1
+ """
2
+ Code taken from https://github.com/Aboghazala/AwesomeTkinter/
3
+ """
4
+
5
+ import tkinter as tk
6
+ from tkinter import ttk
7
+ from .utils import *
8
+ from .config import *
9
+ from .images import *
10
+
11
+
12
+ class Button(tk.Button):
13
+ """A tk MenuButton."""
14
+
15
+ def __init__(
16
+ self, parent, text, h_bg=None, h_fg=None, bg=None, fg=None, command=None, **kw
17
+ ):
18
+ """Initialize the menubutton."""
19
+
20
+ self.bg = bg or get_widget_attribute(parent, "background")
21
+ self.fg = fg or calc_font_color(self.bg)
22
+ h_bg = h_bg or bg
23
+ h_fg = h_fg or fg
24
+ a_fg = h_fg or fg
25
+ a_bg = h_bg or bg
26
+ kw.update(
27
+ dict(
28
+ text=text,
29
+ bg=self.bg,
30
+ fg=self.fg,
31
+ highlightcolor=h_bg,
32
+ activebackground=a_bg,
33
+ activeforeground=a_fg,
34
+ command=command,
35
+ )
36
+ )
37
+ tk.Button.__init__(self, parent, **kw)
38
+ self.bind("<Enter>", self.on_enter)
39
+ self.bind("<Leave>", self.on_leave)
40
+
41
+ def on_enter(self, e):
42
+ self.config(bg=self["activebackground"], fg=self["activeforeground"])
43
+
44
+ def on_leave(self, e):
45
+ self.config(bg=self.bg, fg=self.fg)
46
+
47
+
48
+ class Button3d(ttk.Button):
49
+ """create a button with 3d background color and shadow"""
50
+
51
+ styles = []
52
+
53
+ def __init__(self, parent, bg=None, fg=None, **options):
54
+ """initialize
55
+
56
+ Args:
57
+ parent: tkinter container widget, i.e. root or another frame
58
+ bg (str): button color
59
+ fg (str): text color
60
+ """
61
+ self.bg = bg or DEFAULT_COLOR
62
+ parent_color = get_widget_attribute(parent, "background") or DEFAULT_COLOR
63
+
64
+ # initialize super class
65
+ ttk.Button.__init__(self, parent, **options)
66
+
67
+ # create unique style name based on frame color
68
+ button_style = f"Button3d_{len(Button3d.styles)}_.TButton"
69
+
70
+ # create style
71
+ if button_style not in Button3d.styles:
72
+ self.create_images()
73
+
74
+ # create elements
75
+ s = ttk.Style()
76
+ element_name = f"Button.focus.{button_style}_element"
77
+ s.element_create(
78
+ element_name,
79
+ "image",
80
+ self.img,
81
+ ("pressed", self.pressed_img),
82
+ border=12,
83
+ sticky="nsew",
84
+ )
85
+
86
+ s.layout(
87
+ button_style,
88
+ [
89
+ (
90
+ element_name,
91
+ {
92
+ "sticky": "nswe",
93
+ "children": [
94
+ (
95
+ "Button.padding",
96
+ {
97
+ "sticky": "nswe",
98
+ "children": [
99
+ ("Button.label", {"sticky": "nswe"})
100
+ ],
101
+ },
102
+ )
103
+ ],
104
+ },
105
+ )
106
+ ],
107
+ )
108
+
109
+ s.map(
110
+ button_style,
111
+ background=[("", parent_color)],
112
+ foreground=[("", calc_font_color(self.bg))],
113
+ )
114
+ s.configure(button_style, padding=0, borderwidth=0, focuscolor=self.bg)
115
+ s.configure(button_style, font=FONT)
116
+
117
+ # add to styles
118
+ Button3d.styles.append(button_style)
119
+
120
+ self["style"] = button_style
121
+
122
+ def create_images(self):
123
+
124
+ shadow_img = create_pil_image(b64=btn_base)
125
+ img = create_pil_image(b64=btn_face, color=self.bg)
126
+
127
+ # merge face with base image
128
+ img = mix_images(shadow_img, img)
129
+
130
+ pressed_img = img.rotate(180)
131
+
132
+ self.img = ImageTk.PhotoImage(img)
133
+ self.pressed_img = ImageTk.PhotoImage(pressed_img)
134
+
135
+
136
+ class Radiobutton(ttk.Radiobutton):
137
+ """ttk.Radiobutton with better indicator quality"""
138
+
139
+ styles = []
140
+ imgs = {"outline_img": {}, "selection_img": {}}
141
+
142
+ def __init__(
143
+ self,
144
+ parent,
145
+ text=None,
146
+ ind_bg=None,
147
+ ind_mark_color=None,
148
+ ind_outline_color=None,
149
+ bg=None,
150
+ fg=None,
151
+ font=None,
152
+ value=None,
153
+ **kwargs,
154
+ ):
155
+ """initialize
156
+ Args:
157
+ parent: tkinter container
158
+ text (str): text
159
+ ind_bg (str): indicator ring background "fill color"
160
+ ind_outline_color (str): indicator outline / ring color
161
+ ind_mark_color (str): check mark color
162
+ bg (str): background color "should match parent bg
163
+ fg (str): text color
164
+ font (str): text font, e.g. "any 10 bold"
165
+ value (any): value assigned to button variable when selected
166
+ """
167
+ bg = bg or get_widget_attribute(parent, "background")
168
+ fg = fg or calc_font_color(bg)
169
+ ind_bg = ind_bg or bg
170
+ ind_outline_color = ind_outline_color or fg
171
+ ind_mark_color = ind_mark_color or fg
172
+
173
+ value = value if value is not None else text
174
+
175
+ custom_style = f"RadioButton_{len(Radiobutton.styles)}"
176
+ Radiobutton.styles.append(custom_style)
177
+
178
+ s = ttk.Style()
179
+
180
+ self.outline_img = Radiobutton.imgs["outline_img"].get(ind_outline_color)
181
+ self.selection_img = Radiobutton.imgs["selection_img"].get(ind_mark_color)
182
+
183
+ # create tkinter PhotoImage
184
+ if not (self.outline_img and self.selection_img):
185
+ # create indicator outline
186
+ outline_img = create_circle(
187
+ size=12, thickness=1, color=ind_outline_color, fill=ind_bg, offset=2
188
+ )
189
+
190
+ # create indicator mark
191
+ mark_img = create_circle(
192
+ size=6, thickness=0, color=ind_mark_color, fill=ind_mark_color
193
+ )
194
+
195
+ selection_img = mix_images(outline_img, mark_img)
196
+
197
+ # create tkinter PhotoImage
198
+ self.outline_img = create_image(img=outline_img)
199
+ self.selection_img = create_image(img=selection_img)
200
+
201
+ # store them for future use
202
+ Radiobutton.imgs["outline_img"][ind_outline_color] = self.outline_img
203
+ Radiobutton.imgs["selection_img"][ind_mark_color] = self.selection_img
204
+
205
+ # custom style
206
+ s.layout(
207
+ custom_style,
208
+ [
209
+ (
210
+ "Radiobutton.padding",
211
+ {
212
+ "sticky": "nswe",
213
+ "children": [("Radiobutton.label", {"sticky": "nswe"})],
214
+ },
215
+ )
216
+ ],
217
+ )
218
+ s.configure(custom_style, foreground=fg)
219
+ s.map(custom_style, background=[("", bg)])
220
+
221
+ if font:
222
+ s.configure(custom_style, font=font)
223
+
224
+ # set default options
225
+ options = dict(
226
+ text=text,
227
+ value=value,
228
+ style=custom_style,
229
+ image=[self.outline_img, "selected", self.selection_img],
230
+ compound="left",
231
+ )
232
+
233
+ # override default options
234
+ options.update(**kwargs)
235
+
236
+ # initialize super class
237
+ ttk.Radiobutton.__init__(self, master=parent, **options)
238
+
239
+
240
+ class Checkbutton(tk.Checkbutton):
241
+ """tk.checkbutton with images"""
242
+
243
+ imgs = {"empty_box_img": {}, "checked_box_img": {}}
244
+
245
+ def __init__(
246
+ self,
247
+ parent,
248
+ box_color=None,
249
+ check_mark_color=None,
250
+ text_color=None,
251
+ size=None,
252
+ **kwargs,
253
+ ):
254
+ """initialize
255
+ Args:
256
+ parent: tkinter container
257
+ box_color (str): checkbox outline color
258
+ check_mark_color (str): check mark color
259
+ """
260
+ bg = kwargs.get("bg") or get_widget_attribute(
261
+ parent, "background"
262
+ ) # bg of checkbox
263
+ check_mark_color = check_mark_color or calc_font_color(bg)
264
+ box_color = box_color or calc_font_color(bg)
265
+ text_color = text_color or calc_font_color(bg)
266
+
267
+ # images
268
+ self.empty_box_img = Checkbutton.imgs["empty_box_img"].get(box_color)
269
+ self.checked_box_img = Checkbutton.imgs["checked_box_img"].get(check_mark_color)
270
+
271
+ # create tkinter PhotoImage
272
+ if not (self.empty_box_img and self.checked_box_img):
273
+ self.empty_box_img = create_image(
274
+ b64=unchecked_icon, color=box_color, size=size
275
+ )
276
+ self.checked_box_img = create_image(
277
+ b64=checked_icon, color=check_mark_color, size=size
278
+ )
279
+
280
+ # store images for future use
281
+ Checkbutton.imgs["empty_box_img"][box_color] = self.empty_box_img
282
+ Checkbutton.imgs["checked_box_img"][check_mark_color] = self.checked_box_img
283
+
284
+ # set default options
285
+ options = dict(
286
+ bg=bg,
287
+ activebackground=bg,
288
+ highlightbackground=bg,
289
+ fg=text_color,
290
+ activeforeground=text_color,
291
+ selectcolor=bg,
292
+ compound="left",
293
+ indicatoron=False, # disable original indicator
294
+ image=self.empty_box_img,
295
+ selectimage=self.checked_box_img,
296
+ )
297
+
298
+ # add space before text
299
+ text = kwargs.get("text")
300
+ if text:
301
+ kwargs["text"] = " " + text
302
+
303
+ # override default options
304
+ options.update(**kwargs)
305
+ if "dtype" in kwargs:
306
+ options.pop("dtype")
307
+
308
+ # initialize super class
309
+ tk.Checkbutton.__init__(self, master=parent, **options)
310
+
311
+
312
+ __all__ = ["Button", "Button3d", "Radiobutton", "Checkbutton"]
@@ -0,0 +1,20 @@
1
+ # DEFAULT_COLOR = '#333'
2
+ # ACCENT_COLOR1 = '#00baff'
3
+ # ACCENT_COLOR2 = '#ff00ff'
4
+ DEFAULT_COLOR = "#eeeeee"
5
+ ACCENT_COLOR1 = "#e7e614"
6
+ ACCENT_COLOR2 = "#92278f"
7
+ ACCENT_COLOR3 = "#ed1c24"
8
+ GREEN = "#2d9546"
9
+ RED = "#ed1c24"
10
+ YELLOW = "#f7e615"
11
+ BLACK = "#000"
12
+ TERMINAL_COLOR = "#333"
13
+
14
+ HEADER_FONT = ("Segoe UI", 15, "bold")
15
+ SUBHEADER_FONT = ("Segoe UI", 13, "bold")
16
+ FONT = ("Segoe UI", 11)
17
+ FONT_BOLD = ("Segoe UI", 11, "bold")
18
+ FONT_ITALIC = ("Segoe UI", 11, "italic")
19
+ MENU_FONT = ("Segoe UI", 9)
20
+ TIP_FONT = ("Segoe UI", 9)
@@ -0,0 +1,209 @@
1
+ """
2
+ Code taken from https://github.com/Aboghazala/AwesomeTkinter/
3
+ """
4
+
5
+ import datetime
6
+ import tkinter as tk
7
+ from tkinter import ttk
8
+
9
+ from .utils import *
10
+
11
+
12
+ class DatePicker(tk.Toplevel):
13
+ """Date picker window"""
14
+
15
+ def __init__(
16
+ self,
17
+ master,
18
+ min_year=None,
19
+ max_year=None,
20
+ year=None,
21
+ month=None,
22
+ day=None,
23
+ hour=None,
24
+ minute=None,
25
+ title="Date Picker",
26
+ bg=None,
27
+ fg=None,
28
+ sbg=None,
29
+ btnbg=None,
30
+ btnfg=None,
31
+ width=420,
32
+ height=180,
33
+ ):
34
+ """initialize
35
+
36
+ Args:
37
+ parent: parent window
38
+ min_year (int): minimum year to show
39
+ max_year (int): max. year to show
40
+ year, month, day, hour, minute (int): set selected time
41
+ title (str): window title
42
+ """
43
+ self.master = master
44
+ master_bg = get_widget_attribute(master, "background")
45
+ self.bg = bg or calc_contrast_color(master_bg, 30)
46
+ self.fg = fg or calc_font_color(self.bg)
47
+ self.sbg = sbg or fg
48
+ self.btnbg = btnbg or self.sbg
49
+ self.btnfg = btnfg or calc_font_color(self.btnbg)
50
+
51
+ today = datetime.datetime.today()
52
+ year = year or today.year
53
+ min_year = min_year or year - 100
54
+ max_year = max_year or year + 100
55
+ month = month or today.month
56
+ day = day or today.day
57
+ hour = hour or today.hour
58
+ minute = minute or today.minute
59
+
60
+ self.selected_date = None
61
+
62
+ self.fields = {
63
+ "Year": {"values": list(range(min_year, max_year + 1)), "selection": year},
64
+ "Month": {"values": list(range(1, 13)), "selection": month},
65
+ "Day": {"values": list(range(1, 32)), "selection": day},
66
+ "Hour": {"values": list(range(0, 24)), "selection": hour},
67
+ "Minute": {"values": list(range(0, 60)), "selection": minute},
68
+ }
69
+
70
+ # initialize super
71
+ tk.Toplevel.__init__(self, self.master)
72
+
73
+ # bind window close
74
+ center_window(self, width=width, height=height, reference=self.master)
75
+
76
+ self.title(title)
77
+ self.config(bg=self.sbg)
78
+
79
+ self.create_widgets()
80
+
81
+ self.wait_window(self)
82
+
83
+ def is_leap(self, year):
84
+ """year -> 1 if leap year, else 0, source: datetime.py"""
85
+ return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
86
+
87
+ def days_in_month(self, year, month):
88
+ """year, month -> number of days in that month in that year, modified from source: datetime.py"""
89
+ default = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
90
+
91
+ if month == 2 and self.is_leap(year):
92
+ return 29
93
+ return default[month]
94
+
95
+ def create_widgets(self):
96
+ bg = self.bg
97
+ fg = self.fg
98
+ sbg = self.sbg
99
+ btnbg = self.btnbg
100
+ btnfg = self.btnfg
101
+ main_frame = tk.Frame(self, bg=bg)
102
+ top_frame = tk.Frame(main_frame, bg=bg)
103
+ tk.Label(top_frame, text="Select Date and time:", bg=bg, fg=fg).pack(
104
+ side="left", padx=5, pady=5
105
+ )
106
+
107
+ middle_frame = tk.Frame(main_frame, bg=bg)
108
+
109
+ def set_field(field_name, value):
110
+ x = self.fields[field_name]
111
+ x["selection"] = int(value)
112
+
113
+ # set correct days according to month and year
114
+ year = self.fields["Year"]["selection"]
115
+ month = self.fields["Month"]["selection"]
116
+ day = self.fields["Day"]["selection"]
117
+ days_in_month = self.days_in_month(year, month)
118
+ day_combo.config(values=list(range(1, days_in_month + 1)))
119
+ if day > days_in_month:
120
+ corrected_value = days_in_month
121
+ day_combo.set(corrected_value)
122
+ self.fields["Day"]["selection"] = corrected_value
123
+
124
+ # style
125
+ s = ttk.Style()
126
+ custom_style = "custom.TCombobox"
127
+ # combobox consists of a text area, down arrow, and dropdown menu (listbox)
128
+ # arrow: arrowcolor, background
129
+ # text area: foreground, fieldbackground
130
+ arrow_bg = sbg
131
+ textarea_bg = btnbg
132
+ s.configure(
133
+ custom_style,
134
+ arrowcolor=calc_font_color(arrow_bg),
135
+ foreground=calc_font_color(textarea_bg),
136
+ padding=4,
137
+ relief=tk.RAISED,
138
+ )
139
+ s.map(
140
+ custom_style,
141
+ fieldbackground=[("", textarea_bg)],
142
+ background=[("", arrow_bg)],
143
+ )
144
+
145
+ def on_selection(event):
146
+ widget = event.widget
147
+ widget.selection_clear()
148
+
149
+ widget.callback()
150
+
151
+ c = 0
152
+ for key, item in self.fields.items():
153
+ tk.Label(middle_frame, text=key, bg=bg, fg=fg).grid(
154
+ row=0, column=c, padx=5, sticky="w"
155
+ )
156
+ cb = ttk.Combobox(
157
+ middle_frame,
158
+ state="readonly",
159
+ values=item["values"],
160
+ style=custom_style,
161
+ width=5,
162
+ )
163
+ cb.set(item["selection"])
164
+ cb.grid(row=1, column=c, padx=(5, 10), sticky="w")
165
+ cb.callback = lambda field_name=key, combo=cb: set_field(
166
+ field_name, combo.get()
167
+ )
168
+ cb.bind("<<ComboboxSelected>>", on_selection)
169
+
170
+ # get day combo box reference
171
+ if key == "Day":
172
+ day_combo = cb
173
+
174
+ c += 1
175
+
176
+ bottom_frame = tk.Frame(main_frame, bg=bg)
177
+ tk.Button(
178
+ bottom_frame, text="Cancel", command=self.close, bg=btnbg, fg=btnfg
179
+ ).pack(side="right", padx=5)
180
+ tk.Button(
181
+ bottom_frame, text="Ok", command=self.set_selection, bg=btnbg, fg=btnfg
182
+ ).pack(side="right")
183
+
184
+ main_frame.pack(expand=True, fill="both", padx=(10, 0), pady=(10, 0))
185
+ bottom_frame.pack(side="bottom", fill="x", pady=5)
186
+ ttk.Separator(main_frame).pack(side="bottom", fill="x", expand=True)
187
+ middle_frame.pack(side="bottom", expand=True, fill="both")
188
+ ttk.Separator(main_frame).pack(side="bottom", fill="x", expand=True)
189
+ top_frame.pack(side="bottom", fill="x")
190
+
191
+ def close(self):
192
+ self.destroy()
193
+
194
+ def set_selection(self):
195
+ """return selected date"""
196
+ values = [int(item["selection"]) for item in self.fields.values()]
197
+ self.selected_date = datetime.datetime(*values)
198
+ self.close()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ root = tk.Tk()
203
+
204
+ def foo():
205
+ dp = DatePicker(root, year=1900, day=23, hour=18, bg="white", fg="black")
206
+ print(dp.selected_date)
207
+
208
+ tk.Button(root, text="select date", command=foo).pack()
209
+ root.mainloop()
@@ -0,0 +1,108 @@
1
+ """
2
+ Code taken from https://github.com/Aboghazala/AwesomeTkinter/
3
+ """
4
+
5
+ import platform
6
+ import subprocess
7
+ import shlex
8
+ from tkinter import filedialog
9
+
10
+
11
+ operating_system = (
12
+ platform.system()
13
+ ) # current operating system ('Windows', 'Linux', 'Darwin')
14
+
15
+
16
+ def run_command(cmd):
17
+ try:
18
+ p = subprocess.run(shlex.split(cmd), stdout=subprocess.PIPE, check=True)
19
+ return p.returncode, p.stdout.decode("utf-8").strip()
20
+ except Exception as e:
21
+ # print(e)
22
+ return -1, ""
23
+
24
+
25
+ class FileDialog:
26
+ """use alternative file chooser to replace tkinter ugly file chooser on linux
27
+
28
+ available options:
29
+ Zenity: command line tool, based on gtk
30
+ kdialog: command line tool for kde file chooser
31
+ gtk: use Gtk.FileChooserDialog directly thru python, "Don't not use", since sometimes it will raise
32
+ error: Gdk-Message: Fatal IO error 0 (Success) on X server :1, and application will crash
33
+ """
34
+
35
+ def __init__(self, foldersonly=False):
36
+ self.use = "TK" # , 'zenity', or 'kdialog'
37
+ self.foldersonly = foldersonly
38
+ self.title = "FireDM - "
39
+ self.title += "Select a folder" if self.foldersonly else "Select a file"
40
+ if operating_system == "Linux":
41
+ # looking for zenity
42
+ _, zenity_path = run_command("which zenity")
43
+ if zenity_path:
44
+ self.use = "zenity"
45
+ else:
46
+ # looking for kdialog
47
+ _, kdialog_path = run_command("which kdialog")
48
+ if kdialog_path:
49
+ self.use = "kdialog"
50
+
51
+ def run(self, initialdir="", backend=None):
52
+ selected_path = initialdir
53
+ if backend:
54
+ self.use = backend
55
+
56
+ if self.use == "zenity":
57
+ cmd = "zenity --file-selection"
58
+ if self.foldersonly:
59
+ cmd += " --directory"
60
+ if isinstance(initialdir, str):
61
+ cmd += f' --filename="{initialdir}"'
62
+ retcode, path = run_command(cmd)
63
+ # zenity will return either 0, 1 or 5, depending on whether the user pressed OK,
64
+ # Cancel or timeout has been reached
65
+ if retcode in (0, 1, 5):
66
+ selected_path = path
67
+
68
+ elif self.use == "kdialog":
69
+ cmd = "kdialog"
70
+ if self.foldersonly:
71
+ cmd += " --getexistingdirectory"
72
+ else:
73
+ cmd += " --getopenfilename"
74
+
75
+ if isinstance(initialdir, str):
76
+ cmd += f' "{initialdir}"'
77
+
78
+ retcode, path = run_command(cmd)
79
+ # kdialog will return either 0, 1 depending on whether the user pressed OK, Cancel
80
+ if retcode in (0, 1):
81
+ selected_path = path
82
+ else:
83
+ selected_path = self.run_default(initialdir=initialdir)
84
+
85
+ return selected_path
86
+
87
+ def run_default(self, initialdir=""):
88
+ # use ugly tkinter filechooser
89
+ if self.foldersonly:
90
+ selected_path = filedialog.askdirectory(initialdir=initialdir)
91
+ else:
92
+ selected_path = filedialog.askopenfilename(initialdir=initialdir)
93
+
94
+ return selected_path
95
+
96
+
97
+ filechooser = FileDialog().run
98
+ folderchooser = FileDialog(foldersonly=True).run
99
+
100
+
101
+ if __name__ == "__main__":
102
+ # ret, stdout = run_command('blah blah')
103
+ # print(ret, stdout)
104
+ test_fp = filechooser(backend="kdialog")
105
+ print(test_fp)
106
+ #
107
+ test_dir = folderchooser()
108
+ print(test_dir)