1515__copyright__ = '(c) 2025 Ilya Razmanov'
1616__credits__ = 'Ilya Razmanov'
1717__license__ = 'unlicense'
18- __version__ = '2.19.1.7 '
18+ __version__ = '2.20.4.20 '
1919__maintainer__ = 'Ilya Razmanov'
2020__email__ = 'ilyarazmanov@gmail.com'
2121__status__ = 'Production'
2222
23+ from pathlib import Path
2324from platform import python_version
24- from tkinter import Button , Frame , Label , Menu , PhotoImage , Tk , filedialog
25+ from sys import argv
26+ from time import ctime # Used to show file info only
27+ from tkinter import Button , Frame , Label , Menu , PhotoImage , Tk
28+ from tkinter .filedialog import askopenfilename , asksaveasfilename
2529from tkinter .messagebox import showinfo
2630
2731from pypnm import pnmlpnm
@@ -32,6 +36,14 @@ def DisMiss(event=None) -> None:
3236 sortir .destroy ()
3337
3438
39+ def BindAll () -> None :
40+ """Binding events needed even with no image open"""
41+ sortir .bind_all ('<Button-3>' , ShowMenu )
42+ sortir .bind_all ('<Alt-f>' , ShowMenu )
43+ sortir .bind_all ('<Control-o>' , GetSource )
44+ sortir .bind_all ('<Control-q>' , DisMiss )
45+
46+
3547def UINormal () -> None :
3648 """Normal UI state"""
3749 zanyato .config (state = 'normal' )
@@ -54,41 +66,41 @@ def ShowInfo(event=None) -> None:
5466 showinfo (
5567 title = 'General information' ,
5668 message = f'PNMViewer ver. { __version__ } \n Python: { python_version ()} \n Modules:\n { pnmlpnm .__name__ } ver. { pnmlpnm .__version__ } ' ,
57- detail = f'Image: { sourcefilename } \n X ={ X } , Y={ Y } , Z={ Z } , maxcolors={ maxcolors } ' ,
69+ detail = f'File: \n { sourcefilename } \n Size: { ( Path ( sourcefilename ). stat (). st_size / 1024 ):.2f } kb \n Created: { ctime ( Path ( sourcefilename ). stat (). st_birthtime ) } \n Modified: { ctime ( Path ( sourcefilename ). stat (). st_mtime ) } \n \n Image: X ={ X } , Y={ Y } , Z={ Z } , maxcolors={ maxcolors } ' ,
5870 )
5971
6072
6173def GetSource (event = None ) -> None :
6274 """Opening source image and redefining other controls state"""
6375 global zoom_factor , zoom_do , zoom_show , preview , preview_data
64- global X , Y , Z , maxcolors , image3D , sourcefilename
76+ global X , Y , Z , maxcolors , image3D , sourcefilename , filename_from_command
6577 zoom_factor = 0
6678
67- sourcefilename = filedialog .askopenfilename (title = 'Open PPM/PGM file to view' , filetypes = [('Portable map formats' , '.ppm .pgm .pbm' )])
68- if sourcefilename == '' :
69- return
79+ # ↓ Trying to receive file name from command line, if None, opening GUI
80+ if filename_from_command is None :
81+ sourcefilename = askopenfilename (title = 'Open PPM/PGM file to view' , filetypes = [('Portable map formats' , '.ppm .pgm .pbm' )], initialfile = filename_from_command )
82+ if sourcefilename == '' :
83+ return
84+ else :
85+ sourcefilename = filename_from_command
86+ filename_from_command = None # Removing file name after first open
7087
7188 UIBusy ()
7289
73- """ ┌────────────────────────────────────────┐
74- │ Loading file, converting data to list. │
75- │ NOTE: maxcolors, image3D are GLOBALS! │
76- │ They are used during save! │
77- └────────────────────────────────────────┘ """
78-
90+ # ↓ Loading file, converting data to list.
91+ # NOTE: maxcolors, image3D are GLOBALS!
92+ # They are used during save!
7993 X , Y , Z , maxcolors , image3D = pnmlpnm .pnm2list (sourcefilename )
8094
81- """ ┌─────────────────────────────────────────────────────────────────────────┐
82- │ Converting list to bytes of PPM-like structure "preview_data" in memory │
83- └────────────────────────────────────────────────────────────────────────-┘ """
95+ # ↓ Converting list to bytes of PPM-like structure "preview_data" in memory
8496 preview_data = pnmlpnm .list2bin (image3D , maxcolors )
8597
86- """ ┌────────────────────────────────────────────────┐
87- │ Now showing "preview_data" bytes using Tkinter │
88- └────────────────────────────────────────────────┘ """
98+ # ↓ Now showing "preview_data" bytes using Tkinter
8999 preview = PhotoImage (data = preview_data )
90-
91- zoom_show = { # Text to show below preview
100+ # ↓ Adding filename to window title a-la Photoshop
101+ sortir .title (f'PNMViewer: { Path (sourcefilename ).name } ' )
102+ # ↓ Dictionary of zoom label texts
103+ zoom_show = {
92104 - 4 : 'Zoom 1:5' ,
93105 - 3 : 'Zoom 1:4' ,
94106 - 2 : 'Zoom 1:3' ,
@@ -99,7 +111,8 @@ def GetSource(event=None) -> None:
99111 3 : 'Zoom 4:1' ,
100112 4 : 'Zoom 5:1' ,
101113 }
102- zoom_do = { # Actions to zoom preview; "zoom" zooms in, "subsample" zooms out
114+ # ↓ Dictionary of zoom functions, corresponding to "zoom_show" above
115+ zoom_do = {
103116 - 4 : preview .subsample (5 , 5 ),
104117 - 3 : preview .subsample (4 , 4 ),
105118 - 2 : preview .subsample (3 , 3 ),
@@ -113,21 +126,22 @@ def GetSource(event=None) -> None:
113126
114127 preview = zoom_do [zoom_factor ]
115128 zanyato .config (image = preview , compound = 'none' , borderwidth = 1 , background = zanyato .master ['background' ])
116- # binding zoom on preview click
129+ # ↓ binding zoom on preview click
117130 zanyato .bind ('<Control-Button-1>' , zoomIn ) # Ctrl + left click
118131 zanyato .bind ('<Double-Control-Button-1>' , zoomIn ) # Ctrl + left click too fast
119132 zanyato .bind ('<Alt-Button-1>' , zoomOut ) # Alt + left click
120133 zanyato .bind ('<Double-Alt-Button-1>' , zoomOut ) # Alt + left click too fast
121134 sortir .bind_all ('<MouseWheel>' , zoomWheel ) # Wheel
122135 sortir .bind_all ('<Control-i>' , ShowInfo )
123- # enabling zoom buttons
136+ # ↓ enabling zoom buttons
124137 butt_plus .config (state = 'normal' , cursor = 'hand2' )
125138 butt_minus .config (state = 'normal' , cursor = 'hand2' )
126- # updating zoom label display
139+ # ↓ updating zoom label display
127140 label_zoom .config (text = zoom_show [zoom_factor ])
128- # enabling "Save as..."
141+ # ↓ enabling "Save as..."
129142 menu01 .entryconfig ('Save binary PNM...' , state = 'normal' ) # Instead of name numbers from 0 may be used
130143 menu01 .entryconfig ('Save ascii PNM...' , state = 'normal' )
144+ menu01 .entryconfig ('Info' , state = 'normal' )
131145
132146 UINormal ()
133147
@@ -143,18 +157,16 @@ def SaveAsPNM(bin: bool) -> None:
143157 extension = ('Portable pixel map' , '.ppm' )
144158 filetype = 'PPM'
145159
146- # Open "Save as..." file
147- savefilename = filedialog . asksaveasfilename (
160+ # ↓ Open "Save as..." file
161+ savefilename = asksaveasfilename (
148162 title = f'Save { filetype } file' ,
149163 filetypes = format ,
150164 defaultextension = extension ,
151165 )
152166 if savefilename == '' :
153167 return
154168
155- """ ┌────────────────────────────────────────────────────┐
156- │ Saving "savefilename" in format depending on "bin" │
157- └───────────────────────────────────────────────────-┘ """
169+ # ↓ Saving "savefilename" in PNM format depending on "bin"
158170 UIBusy ()
159171 pnmlpnm .list2pnm (savefilename , image3D , maxcolors , bin )
160172 UINormal ()
@@ -167,9 +179,9 @@ def zoomIn(event=None) -> None:
167179 preview = PhotoImage (data = preview_data )
168180 preview = zoom_do [zoom_factor ]
169181 zanyato .config (image = preview , compound = 'none' )
170- # updating zoom factor display
182+ # ↓ updating zoom factor display
171183 label_zoom .config (text = zoom_show [zoom_factor ])
172- # reenabling +/- buttons
184+ # ↓ reenabling +/- buttons
173185 butt_minus .config (state = 'normal' , cursor = 'hand2' )
174186 if zoom_factor == 4 : # max zoom 5
175187 butt_plus .config (state = 'disabled' , cursor = 'arrow' )
@@ -184,9 +196,9 @@ def zoomOut(event=None) -> None:
184196 preview = PhotoImage (data = preview_data )
185197 preview = zoom_do [zoom_factor ]
186198 zanyato .config (image = preview , compound = 'none' )
187- # updating zoom factor display
199+ # ↓ updating zoom factor display
188200 label_zoom .config (text = zoom_show [zoom_factor ])
189- # reenabling +/- buttons
201+ # ↓ reenabling +/- buttons
190202 butt_plus .config (state = 'normal' , cursor = 'hand2' )
191203 if zoom_factor == - 4 : # min zoom 1/5
192204 butt_minus .config (state = 'disabled' , cursor = 'arrow' )
@@ -210,12 +222,9 @@ def zoomWheel(event) -> None:
210222sourcefilename = X = Y = Z = maxcolors = None
211223
212224sortir = Tk ()
213-
214225sortir .title ('PNMViewer' )
215226sortir .geometry ('+200+100' )
216227sortir .minsize (128 , 128 )
217-
218- # Main dialog icon is PPM as well!
219228sortir .iconphoto (True , PhotoImage (data = b'P6\n 2 2\n 255\n \xff \x00 \x00 \xff \xff \x00 \x00 \x00 \xff \x00 \xff \x00 ' ))
220229
221230menu01 = Menu (sortir , tearoff = False ) # Main menu, currently one "File" entry
@@ -225,15 +234,10 @@ def zoomWheel(event) -> None:
225234menu01 .add_command (label = 'Save binary PNM...' , state = 'disabled' , command = lambda : SaveAsPNM (bin = True ))
226235menu01 .add_command (label = 'Save ascii PNM...' , state = 'disabled' , command = lambda : SaveAsPNM (bin = False ))
227236menu01 .add_separator ()
228- menu01 .add_command (label = 'Info' , accelerator = 'Ctrl+I' , command = ShowInfo )
237+ menu01 .add_command (label = 'Info' , accelerator = 'Ctrl+I' , state = 'disabled' , command = ShowInfo )
229238menu01 .add_separator ()
230239menu01 .add_command (label = 'Exit' , state = 'normal' , accelerator = 'Ctrl+Q' , command = DisMiss )
231240
232- sortir .bind ('<Button-3>' , ShowMenu )
233- sortir .bind_all ('<Alt-f>' , ShowMenu )
234- sortir .bind_all ('<Control-o>' , GetSource )
235- sortir .bind_all ('<Control-q>' , DisMiss )
236-
237241frame_img = Frame (sortir , borderwidth = 2 , relief = 'groove' )
238242frame_img .pack (side = 'top' )
239243
@@ -265,4 +269,19 @@ def zoomWheel(event) -> None:
265269label_zoom = Label (frame_zoom , text = 'Zoom 1:1' , font = ('courier' , 8 ), state = 'disabled' )
266270label_zoom .pack (side = 'left' , anchor = 'n' , padx = 2 , pady = 0 , fill = 'both' )
267271
272+ BindAll ()
273+
274+ # ↓ Command line part
275+ if len (argv ) == 2 :
276+ try_to_open = argv [1 ]
277+ if Path (try_to_open ).exists () and Path (try_to_open ).is_file () and (Path (try_to_open ).suffix in ('.ppm' , '.pgm' , '.pbm' )):
278+ filename_from_command = str (Path (try_to_open ).resolve ())
279+ GetSource ()
280+ else :
281+ filename_from_command = None
282+ sortir .focus_force () # Otherwise loses focus when run from command line
283+ else :
284+ filename_from_command = None
285+ sortir .focus_force () # Otherwise loses focus when run from command line
286+
268287sortir .mainloop ()
0 commit comments