Besides of that I support the idea of having constant values like window's height, width and input_row settings as class constants (uppercase names) and referencing negative kcell flag as not kcell instead of kcell == False
here's some list of advises in terms of better code organizing, restructuring conditionals and eliminating duplicates:
window's half of
widthint(self.winwidth / 2)is calculated 13 times across 3 methods.
Instead, we'll apply Extract variable technique, extracting precalculated expression into the instance fieldself.win_width_half = int(self.winwidth / 2)and referencing it in all places it's needed
(likeself.inputcol = [[36, 50], [self.win_width_half + 36, self.win_width_half + 50]])setting "windows entry points" in
initScreenmethod (should be renamed toinit_screen): involves consecutive 20 calls ofself.measurewin.addstr(...)function.
Instead, we can define entry points attributes beforehand and then pass them in simple iteration:win_entry_points_attrs = [ (2, self.inputcol[0][0] - 3, "1", self.white), (2, self.inputcol[0][1] - 3, "2", self.white), (2, self.inputcol[1][0] - 3, "1", self.white), (2, self.inputcol[1][1] - 3, "2", self.white), (5, 5, "A"), (9, 5, "B"), (13, 5, "C"), (17, 5, "D"), (16, 20, "D.I"), (18, 20, "D.II"), (22, 5, "E"), (21, 20, "E.I"), (23, 20, "E.II"), (5, self.win_width_half + 5, "F"), (9, self.win_width_half + 5, "G"), (13, self.win_width_half + 5, "H"), (12, self.win_width_half + 20, "H.I"), (14, self.win_width_half + 20, "H.II"), (18, self.win_width_half + 5, "J"), (17, self.win_width_half + 20, "J.I"), (19, self.win_width_half + 20, "J.II"), ] # print the windows entry points for attrs in win_entry_points_attrs: self.measurewin.addstr(*attrs)
Optimizations within inputMeasures method (should be renamed to input_measures):
the condition:
if ((ch == curses.KEY_UP and j > 0) or (ch == curses.KEY_DOWN and kcell == False) or (ch == curses.KEY_LEFT and (i != 0 or k != 0) and kcell == False) or (ch == curses.KEY_RIGHT and (i != 1 or k != 1) and kcell == False) or (ch in [ord("s"), ord("S")]) or (ch in [ord("q"), ord("Q")])): breakhas a common check
kcell == False(should benot kcell) in 3 branches (in the middle) and that's a sign of Consolidate conditional refactoring technique:if ((ch == curses.KEY_UP and j > 0) or (not kcell and (ch == curses.KEY_DOWN or (ch == curses.KEY_LEFT and (i != 0 or k != 0)) or (ch == curses.KEY_RIGHT and (i != 1 or k != 1)))) or (chr(ch).lower() in ("s", "q"))): breakthe variable
cursorcntr(cursor counter) deserves for a more meaningful variable name (Rename variable technique) - I would suggestcursor_cntthe last complex
if .. elif .. elifconditional of 6 branches at the end of methodinput_measuresseems to be a good candidate for Replace Conditional with Polymorphism technique (OOP approach) but that would require more knowledge and vision about your program conception/settings.
For now, the first 4 branches of that conditional perform the same actioncursor_cnt *= 2- we can eliminate duplication with additional check.
It's good to move "save/quit" branches up, as they callself.exit()which will throwraise SystemExitto exit the program.
Thus the reorganized conditional would look as:# set of key codes defined in the parent scope (at least before the loop or as instance variable) key_set = set((curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT)) ... # check If the user wants to save/quit inp_char = chr(ch).lower() if inp_char == 's': self.exit("save") elif inp_char == 'q': self.exit("quit") if ch in key_set: cursor_cnt *= 2 if ch == curses.KEY_UP: if kcell: kcell = False else: self.cirow -= 1 # If I pressed an arrow key the value of the cursor counter is always set to a # multiple of two (which means that when an arrow key is entered I will always # get a blinking cursos in the destination cell) elif ch == curses.KEY_DOWN: if (i == 0 and j == 6) or (i == 1 and j == 5): kcell = True else: self.cirow += 1 elif ch == curses.KEY_LEFT: self.cicol -= 1 if i == 1 and k == 0: self.ciside -= 1 self.cicol += 2 elif ch == curses.KEY_RIGHT: self.cicol += 1 if i == 0 and k == 1: self.ciside += 1 self.cicol -= 2 if self.cirow == 6: self.cirow -= 1
Any examples of slick curses user interfaces built with Python? (post screenshots please!)
I'm trying to make a fancy CLI interface in python. Curses is very confusing (and I can't find a good tutorial!) Any suggestions?
Videos
Besides of that I support the idea of having constant values like window's height, width and input_row settings as class constants (uppercase names) and referencing negative kcell flag as not kcell instead of kcell == False
here's some list of advises in terms of better code organizing, restructuring conditionals and eliminating duplicates:
window's half of
widthint(self.winwidth / 2)is calculated 13 times across 3 methods.
Instead, we'll apply Extract variable technique, extracting precalculated expression into the instance fieldself.win_width_half = int(self.winwidth / 2)and referencing it in all places it's needed
(likeself.inputcol = [[36, 50], [self.win_width_half + 36, self.win_width_half + 50]])setting "windows entry points" in
initScreenmethod (should be renamed toinit_screen): involves consecutive 20 calls ofself.measurewin.addstr(...)function.
Instead, we can define entry points attributes beforehand and then pass them in simple iteration:win_entry_points_attrs = [ (2, self.inputcol[0][0] - 3, "1", self.white), (2, self.inputcol[0][1] - 3, "2", self.white), (2, self.inputcol[1][0] - 3, "1", self.white), (2, self.inputcol[1][1] - 3, "2", self.white), (5, 5, "A"), (9, 5, "B"), (13, 5, "C"), (17, 5, "D"), (16, 20, "D.I"), (18, 20, "D.II"), (22, 5, "E"), (21, 20, "E.I"), (23, 20, "E.II"), (5, self.win_width_half + 5, "F"), (9, self.win_width_half + 5, "G"), (13, self.win_width_half + 5, "H"), (12, self.win_width_half + 20, "H.I"), (14, self.win_width_half + 20, "H.II"), (18, self.win_width_half + 5, "J"), (17, self.win_width_half + 20, "J.I"), (19, self.win_width_half + 20, "J.II"), ] # print the windows entry points for attrs in win_entry_points_attrs: self.measurewin.addstr(*attrs)
Optimizations within inputMeasures method (should be renamed to input_measures):
the condition:
if ((ch == curses.KEY_UP and j > 0) or (ch == curses.KEY_DOWN and kcell == False) or (ch == curses.KEY_LEFT and (i != 0 or k != 0) and kcell == False) or (ch == curses.KEY_RIGHT and (i != 1 or k != 1) and kcell == False) or (ch in [ord("s"), ord("S")]) or (ch in [ord("q"), ord("Q")])): breakhas a common check
kcell == False(should benot kcell) in 3 branches (in the middle) and that's a sign of Consolidate conditional refactoring technique:if ((ch == curses.KEY_UP and j > 0) or (not kcell and (ch == curses.KEY_DOWN or (ch == curses.KEY_LEFT and (i != 0 or k != 0)) or (ch == curses.KEY_RIGHT and (i != 1 or k != 1)))) or (chr(ch).lower() in ("s", "q"))): breakthe variable
cursorcntr(cursor counter) deserves for a more meaningful variable name (Rename variable technique) - I would suggestcursor_cntthe last complex
if .. elif .. elifconditional of 6 branches at the end of methodinput_measuresseems to be a good candidate for Replace Conditional with Polymorphism technique (OOP approach) but that would require more knowledge and vision about your program conception/settings.
For now, the first 4 branches of that conditional perform the same actioncursor_cnt *= 2- we can eliminate duplication with additional check.
It's good to move "save/quit" branches up, as they callself.exit()which will throwraise SystemExitto exit the program.
Thus the reorganized conditional would look as:# set of key codes defined in the parent scope (at least before the loop or as instance variable) key_set = set((curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT)) ... # check If the user wants to save/quit inp_char = chr(ch).lower() if inp_char == 's': self.exit("save") elif inp_char == 'q': self.exit("quit") if ch in key_set: cursor_cnt *= 2 if ch == curses.KEY_UP: if kcell: kcell = False else: self.cirow -= 1 # If I pressed an arrow key the value of the cursor counter is always set to a # multiple of two (which means that when an arrow key is entered I will always # get a blinking cursos in the destination cell) elif ch == curses.KEY_DOWN: if (i == 0 and j == 6) or (i == 1 and j == 5): kcell = True else: self.cirow += 1 elif ch == curses.KEY_LEFT: self.cicol -= 1 if i == 1 and k == 0: self.ciside -= 1 self.cicol += 2 elif ch == curses.KEY_RIGHT: self.cicol += 1 if i == 0 and k == 1: self.ciside += 1 self.cicol -= 2 if self.cirow == 6: self.cirow -= 1
I can't run this unfortunately. curses seems to have issues on Windows. I'll just focus mainly on style and design.
There's a few notable things about this chunk:
# I also exit the loop if one of the following conditions if verified
if ((ch == curses.KEY_UP and j > 0)
or (ch == curses.KEY_DOWN and kcell == False)
or (ch == curses.KEY_LEFT and (i != 0 or k != 0) and kcell == False)
or (ch == curses.KEY_RIGHT and (i != 1 or k != 1) and kcell == False)
or (ch in [ord("s"), ord("S")])
or (ch in [ord("q"), ord("Q")])):
break
== Falseshould really just benotinstead- You need more indentation. It's confusing to see the
ors aligned with theifs. I'd indent it at least all the way up to align with the opening brace. - Those two
ch inchecks at the bottom could be cleaned up.
I'd write this closer to:
if ((ch == curses.KEY_UP and j > 0)
or (ch == curses.KEY_DOWN and not kcell)
or (ch == curses.KEY_LEFT and (i != 0 or k != 0) and not kcell)
or (ch == curses.KEY_RIGHT and (i != 1 or k != 1) and not kcell)
or (chr(ch).lower() in {"s", "q"})):
break
I think you could probably factor out the not kcell check too, but my tired brain can't think of a good way at the moment.
There is another approach though that lets you skip all the ors: any. any is like a function version of or (and all is like and). You could change this to:
if any([ch == curses.KEY_UP and j > 0,
ch == curses.KEY_LEFT and (i != 0 or k != 0) and not kcell,
ch == curses.KEY_RIGHT and (i != 1 or k != 1) and not kcell,
chr(ch).lower() in {"s", "q"}]):
break
Normally, I'd say that this is an abuse of any, but chaining ors over multiple lines like you had isn't ideal either.
In terms of design decisions, does this really need to be a class? Honestly, I'd probably store the necessary state using a dataclass or NamedTuple (or a couple distinct states), then just pass around and alter state(s) as needed. Making self a grab-bag of everything you may need seem messy to me.
For example, winwidth and winheight appear to be constants. They never change throughout your program, so they should treated as constants. I'd have them outside at the top as:
WINDOW_WIDTH = 120
WINDOW_HEIGHT = 29
And do ciside, cicol and cirow need to be attributes of the class as well? It seems like they're only used in inputMeasures, so why aren't they just variables local to that function? By having them "global" within the object, you're forcing your reader to keep them in the back of their mind in case those states are needed elsewhere in the object.
Finally, in terms of naming, you're violating PEP8. Variable and function names should be in snake_case unless you have a good reason, and class names should be UpperCamelCase.
I'm wondering if there are any slick looking interfaces built in curses with Python. If you know any, please post screenshots!
As the title suggests, I'm trying to make an interactive CLI interface for a small python app I've written. Basically, I need to be able to print characters anywhere on the screen and change their colors.
I've tried messing around with curses, but the API is horrendously complex. It also seems next-to-impossible to find a tutorial for the python module. Can anybody suggest a decent alternative or a decent tutorial for curses?