├── src └── menufather │ ├── __init__.py │ └── menuMaker.py ├── Pictures ├── result1.png ├── source1.png ├── source2.png └── toomanyitem.gif ├── dist ├── menufather-1.5.0.tar.gz └── menufather-1.5.0-py3-none-any.whl ├── Examples ├── first_menu.py ├── too_many_items.py └── reCreate_example.py ├── pyproject.toml ├── LICENSE └── README.md /src/menufather/__init__.py: -------------------------------------------------------------------------------- 1 | from .menuMaker import Menu 2 | __version__ = 1.0 3 | 4 | -------------------------------------------------------------------------------- /Pictures/result1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/Pictures/result1.png -------------------------------------------------------------------------------- /Pictures/source1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/Pictures/source1.png -------------------------------------------------------------------------------- /Pictures/source2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/Pictures/source2.png -------------------------------------------------------------------------------- /Pictures/toomanyitem.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/Pictures/toomanyitem.gif -------------------------------------------------------------------------------- /dist/menufather-1.5.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/dist/menufather-1.5.0.tar.gz -------------------------------------------------------------------------------- /dist/menufather-1.5.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrpythonblog/menufather/HEAD/dist/menufather-1.5.0-py3-none-any.whl -------------------------------------------------------------------------------- /Examples/first_menu.py: -------------------------------------------------------------------------------- 1 | import menufather 2 | import time 3 | 4 | title = "Title of menu" 5 | items = ["First Button" , "Second Button" , "About" , "Exit"] 6 | menu = menufather.Menu(title = title , items = items) 7 | menu.show() # show the menu 8 | 9 | while True: # our program main loop 10 | time.sleep(1) # this is important !! 11 | selected = menu.get_selected_item() 12 | if selected != None : 13 | break 14 | 15 | print("selected item : {}".format(items[selected])) 16 | 17 | -------------------------------------------------------------------------------- /Examples/too_many_items.py: -------------------------------------------------------------------------------- 1 | import menufather 2 | import time 3 | 4 | title = "Title of menu" 5 | items = [] 6 | for i in range(1,101): 7 | items.append("Item {}".format(i)) 8 | 9 | menu = menufather.Menu(title = title , items = items) 10 | menu.show() # show the menu 11 | 12 | while True: # our program main loop 13 | time.sleep(1) # this is important !! 14 | selected = menu.get_selected_item() 15 | if selected != None : 16 | break 17 | 18 | print("selected item : {}".format(items[selected])) 19 | 20 | -------------------------------------------------------------------------------- /Examples/reCreate_example.py: -------------------------------------------------------------------------------- 1 | import menufather 2 | import time 3 | 4 | title1 = "Title of menu 1" 5 | title2 = "Title of menu 2" 6 | items1 = ["goto menu 2" , "alaki" , "About" , "Exit"] 7 | items2 = ["first item" , "second item" , "back"] 8 | menu = menufather.Menu(title = title1 , items = items1) 9 | menu.show() # show the menu 10 | 11 | while True: # our program main loop 12 | time.sleep(1) # this is important !! 13 | selected = menu.get_selected_item() 14 | if selected == 0: # (goto menu 2) item 15 | menu.reCreate(title2 , items2) 16 | 17 | 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # pyproject.toml 2 | 3 | [build-system] 4 | requires = ["setuptools>=61.0.0", "wheel"] 5 | build-backend = "setuptools.build_meta" 6 | 7 | [project] 8 | name = "menufather" 9 | version = "1.5.0" 10 | description = "a library for creating console-based menus (linux / windows)" 11 | readme = "README.md" 12 | authors = [{ name = "MrPythonBlog", email = "mrpythonblog@gmail.com" }] 13 | license = { file = "LICENSE" } 14 | classifiers = [ 15 | "License :: OSI Approved :: MIT License", 16 | "Programming Language :: Python", 17 | "Programming Language :: Python :: 3", 18 | ] 19 | dependencies = [ 20 | "colorama", 21 | "cursor", 22 | "pynput"] 23 | 24 | requires-python = ">=3.5" 25 | 26 | [project.urls] 27 | Homepage = "https://github.com/mrpythonblog/menufather" 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MrPythonBlog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Persian Document](https://mrpython.blog.ir/post/149) 2 | 3 | # menufather Ver 1.0 4 | **menufather** is a simple and lightweight python library for creating console-based menus (windows / linux) . 5 | 6 | # INSTALLATION 7 | it can be installed using **pip** : 8 | **Linux** : ```python3 -m pip install menufather``` 9 | or **Windows** : ``` pip install menufather``` 10 | 11 | 12 | # Creating the first menu ! 13 | ![creating the first menu](https://github.com/mrpythonblog/menufather/raw/main/Pictures/source1.png) 14 | 15 | at first we declare our menu items in the "items" list as strings . 16 | then we create a menu using ```menufather.Menu(title , items)``` function . after that , we can show our menu using ```menu.show()``` method. 17 | * we should make a loop in our program . 18 | * in our while loop we should use a **sleep** (ex : 0.5s sleep) 19 | 20 | 21 | ```menu.get_selected_item()``` can be used for getting the selected item by user . it returns the index of selected item in "items" list if user has selected an item else it returns **None** 22 | once we get selected item by this function , the next round this function returns **None** until user selects another item so we should save the result of this function in a variable at the first of loop (```selected``` variable) . 23 | 24 | Result : 25 | ![result 1](https://github.com/mrpythonblog/menufather/raw/main/Pictures/result1.png) 26 | 27 | # Locking and Unlocking Menu 28 | the two methods ```menu.lock()``` and ```menu.unlock()``` , can lock or unlock the menu . when menu is locked , user can't navigate or select anything on it . 29 | 30 | # Updating menu items 31 | we can update an item in the menu using ```menu.updateItem(itemIndex , new)``` . ```itemIndex``` is the index of item in the "items" list that we want to update and ```new``` is the string value we want to replace . 32 | 33 | example : ```menu.updateItem(2 , "Contact")``` . this changes item index 2 in the menu to "Contact" . 34 | 35 | # ReCreating the menu 36 | sometimes we want to have some items that can create a new menu when user selects that . ```menu.reCreate(new_title , new_items)``` can do this . ```new_title``` is the title of the new menu and ```new_items``` is a list that contains the items of new menu . 37 | 38 | example : 39 | ![reCreate menu example](https://github.com/mrpythonblog/menufather/raw/main/Pictures/source2.png) 40 | 41 | after running this source , if we select "goto menu 2" item , a new menu appears (menu 2) ... 42 | 43 | 44 | # Auto Scrolling 45 | if your items are too many , don't worry ! menufather simulate a scrolling state for items ! 46 | 47 | 48 | 49 | 50 | ‍ 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/menufather/menuMaker.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | from colorama import init ,Fore,Back,Style 4 | import cursor 5 | from sys import platform,stdin 6 | from pynput import keyboard 7 | import re 8 | 9 | 10 | def check_os(): 11 | if platform.startswith("win32"): # windows 12 | global GetWindowText,GetForegroundWindow,msvcrt 13 | from win32gui import GetWindowText , GetForegroundWindow 14 | import msvcrt 15 | return "windows" 16 | elif platform.startswith("linux"): # linux 17 | global tcflush,TCIOFLUSH 18 | from termios import tcflush,TCIOFLUSH 19 | return "linux" 20 | 21 | def Menu(title , items): 22 | if check_os() == "windows": 23 | return menu_windows(title, items) 24 | elif check_os() == "linux": 25 | return menu_linux(title , items) 26 | 27 | class menu_windows: 28 | def __init__(self ,title, items): 29 | 30 | self.platform = "windows" 31 | self.item_per_menu = 10 32 | self.window_title = GetWindowText(GetForegroundWindow()) 33 | self.title = title 34 | self.items = items.copy() 35 | self.initItems() # make sure all items of items list have a fix and equal size . after every change in item list we should call this function 36 | self.current_item = 0 37 | self.current_item_section = 0 38 | self.selected_item = None 39 | self.locked = False 40 | self.statusbar = "" 41 | 42 | init() # Colorama init() 43 | listener = keyboard.Listener(on_press=self.on_press_key) # set keyboard listener 44 | listener.start() 45 | 46 | def show(self): 47 | self.banner = "" 48 | os.system("cls") 49 | cursor.hide() 50 | self.banner += "\n" #print() 51 | self.banner += self.title.center(os.get_terminal_size()[0]) + "\n" #print("Title".center(os.get_terminal_size()[0])) 52 | self.banner += ("-" * 30).center(os.get_terminal_size()[0]) + "\n" #print(("-" * 30).center(os.get_terminal_size()[0])) 53 | self.banner += "\n" #print() 54 | 55 | for item in self.items_sections[self.current_item_section]: # CHANGE FOR SCROLLING PROBLEM 56 | option = " "*10+"> "+Back.WHITE+Fore.BLACK if self.items.index(item) == self.current_item else " " 57 | option += " " 58 | option += item 59 | option += ""+Style.RESET_ALL 60 | option = option.center(os.get_terminal_size()[0]) + "\n" 61 | self.banner += option 62 | 63 | 64 | if len(self.items) > 5 and self.current_item_section < len(self.items_sections) - 1: 65 | self.banner += (" "*12 + Back.WHITE + Fore.BLACK + ">>>" + Style.RESET_ALL).center(os.get_terminal_size()[0]) + "\n" 66 | 67 | self.banner = "\n"*(os.get_terminal_size()[1]//2 - self.banner.count("\n") // 2) + self.banner 68 | self.banner += self.statusbar 69 | print(self.banner) 70 | 71 | 72 | def on_press_key(self,key): 73 | if GetWindowText(GetForegroundWindow()) == self.window_title: # if user was on application's window 74 | try: 75 | char = key.char 76 | except: 77 | name = key.name 78 | if name == "up": 79 | if not self.locked: 80 | if self.current_item > 0 : 81 | self.current_item -= 1 82 | self.current_item_section = self.current_item // self.item_per_menu 83 | self.show() 84 | elif name == "down": 85 | if not self.locked: 86 | if self.current_item < len(self.items) - 1: 87 | self.current_item += 1 88 | self.current_item_section = self.current_item // self.item_per_menu 89 | self.show() 90 | elif name == "enter": 91 | if not self.locked: 92 | self.selected_item = self.current_item 93 | self.flush_buffer() # flush keyboard input buffer 94 | 95 | 96 | def flush_buffer(self): 97 | while msvcrt.kbhit(): 98 | msvcrt.getch() 99 | 100 | def clear_selected_item(self): 101 | self.selected_item = None 102 | 103 | def get_selected_item(self): 104 | item = self.selected_item 105 | self.clear_selected_item() 106 | return item 107 | 108 | def updateItem(self , itemIndex , new): 109 | self.items[itemIndex] = new 110 | self.initItems(self.current_item_section) 111 | self.show() 112 | 113 | def lock(self): 114 | self.locked = True 115 | 116 | def unlock(self): 117 | self.locked = False 118 | 119 | def reCreate(self ,new_title , new_items): # for clear and reCreate a new menu 120 | self.title = new_title 121 | self.items = new_items 122 | self.current_item = 0 123 | self.initItems() 124 | self.clear_selected_item() 125 | self.show() 126 | 127 | def initItems(self,current_item_section = 0): 128 | max_length = 0 129 | for i in self.items: 130 | i = i.strip(" ") 131 | if len(i) > max_length: 132 | max_length = len(i) 133 | 134 | for i in range(len(self.items)): 135 | self.items[i] = self.items[i].strip(" ") 136 | self.items[i] += (max_length - len(self.items[i])) * " " 137 | 138 | 139 | self.current_item_section = current_item_section 140 | self.items_sections = [] 141 | for i in range(0 , len(self.items) , self.item_per_menu): 142 | self.items_sections.append(self.items[i:i+self.item_per_menu]) 143 | 144 | 145 | def setStatusBar(self,status): 146 | if len(status) > 0: 147 | self.statusbar = "\n" 148 | for line in (status.split("\n")): 149 | self.statusbar += line.center(os.get_terminal_size()[0]) 150 | self.statusbar += "\n" 151 | 152 | else: 153 | self.statusbar = "" 154 | 155 | self.show() 156 | 157 | 158 | class menu_linux: 159 | def __init__(self ,title, items): 160 | 161 | self.item_per_menu = 10 162 | self.platform = "linux" 163 | self.window_id = self.get_active_window() 164 | self.title = title 165 | self.items = items.copy() 166 | self.initItems() # make sure all items of items list have a fix and equal size . after every change in item list we should call this function 167 | self.current_item = 0 168 | self.current_item_section = 0 169 | self.selected_item = None 170 | self.locked = False 171 | self.statusbar = "" 172 | 173 | init() # Colorama init() 174 | listener = keyboard.Listener(on_press=self.on_press_key) # set keyboard listener 175 | listener.start() 176 | 177 | 178 | def get_active_window(self): 179 | process = subprocess.Popen(["xprop" , "-root" , "_NET_ACTIVE_WINDOW"] , stdout = subprocess.PIPE) 180 | stdout = process.communicate() 181 | stdout = stdout[0].decode().strip("\n") 182 | window_id = re.search('^_NET_ACTIVE_WINDOW.* ([\w]+)$', stdout) 183 | window_id = window_id.group(1) 184 | return window_id 185 | 186 | def show(self): 187 | self.banner = "" 188 | os.system("clear") 189 | cursor.hide() 190 | self.banner += "\n" #print() 191 | self.banner += self.title.center(os.get_terminal_size()[0]) + "\n" #print("Title".center(os.get_terminal_size()[0])) 192 | self.banner += ("-" * 30).center(os.get_terminal_size()[0]) + "\n" #print(("-" * 30).center(os.get_terminal_size()[0])) 193 | self.banner += "\n" #print() 194 | 195 | for item in self.items_sections[self.current_item_section]: # CHANGE FOR SCROLLING PROBLEM 196 | option = " "*10+"> "+Back.WHITE+Fore.BLACK if self.items.index(item) == self.current_item else " " 197 | option += " " 198 | option += item 199 | option += ""+Style.RESET_ALL 200 | option = option.center(os.get_terminal_size()[0]) + "\n" 201 | self.banner += option 202 | 203 | 204 | if len(self.items) > 5 and self.current_item_section < len(self.items_sections) - 1: 205 | self.banner += (" "*12 + Back.WHITE + Fore.BLACK + ">>>" + Style.RESET_ALL).center(os.get_terminal_size()[0]) + "\n" 206 | 207 | self.banner = "\n"*(os.get_terminal_size()[1]//2 - self.banner.count("\n") // 2) + self.banner 208 | self.banner += self.statusbar 209 | print(self.banner) 210 | 211 | def on_press_key(self,key): 212 | if self.get_active_window() == self.window_id: # if user was on application's window 213 | try: 214 | char = key.char 215 | except: 216 | name = key.name 217 | if name == "up": 218 | if not self.locked: 219 | if self.current_item > 0 : 220 | self.current_item -= 1 221 | self.current_item_section = self.current_item // self.item_per_menu 222 | self.show() 223 | elif name == "down": 224 | if not self.locked: 225 | if self.current_item < len(self.items) - 1: 226 | self.current_item += 1 227 | self.current_item_section = self.current_item // self.item_per_menu 228 | self.show() 229 | elif name == "enter": 230 | if not self.locked: 231 | self.selected_item = self.current_item 232 | tcflush(stdin , TCIOFLUSH) # flush keyboard input buffer 233 | 234 | 235 | 236 | def clear_selected_item(self): 237 | self.selected_item = None 238 | 239 | def get_selected_item(self): 240 | item = self.selected_item 241 | self.clear_selected_item() 242 | return item 243 | 244 | def updateItem(self , itemIndex , new): 245 | self.items[itemIndex] = new 246 | self.initItems(self.current_item_section) 247 | self.show() 248 | 249 | def lock(self): 250 | self.locked = True 251 | 252 | def unlock(self): 253 | self.locked = False 254 | 255 | def reCreate(self ,new_title , new_items): # for clear and reCreate a new menu 256 | self.title = new_title 257 | self.items = new_items 258 | self.current_item = 0 259 | self.initItems() 260 | self.clear_selected_item() 261 | self.show() 262 | 263 | def initItems(self,current_item_section = 0): 264 | max_length = 0 265 | for i in self.items: 266 | i = i.strip(" ") 267 | if len(i) > max_length: 268 | max_length = len(i) 269 | 270 | for i in range(len(self.items)): 271 | self.items[i] = self.items[i].strip(" ") 272 | self.items[i] += (max_length - len(self.items[i])) * " " 273 | 274 | 275 | self.current_item_section = current_item_section 276 | self.items_sections = [] 277 | for i in range(0 , len(self.items) , self.item_per_menu): 278 | self.items_sections.append(self.items[i:i+self.item_per_menu]) 279 | 280 | 281 | def setStatusBar(self,status): 282 | if len(status) > 0: 283 | self.statusbar = "\n" 284 | for line in (status.split("\n")): 285 | self.statusbar += line.center(os.get_terminal_size()[0]) 286 | self.statusbar += "\n" 287 | 288 | else: 289 | self.statusbar = "" 290 | 291 | self.show() 292 | --------------------------------------------------------------------------------