]> git.armaanb.net Git - lightcards.git/blobdiff - lightcards/display.py
Fix purge option
[lightcards.git] / lightcards / display.py
index f51259396cd04cd2587cbdf92f993267531159aa..0e1c01c7c35a0886cb3722d3e153d879177ab517 100644 (file)
@@ -6,6 +6,7 @@ import curses.panel
 from random import shuffle
 import sys
 import textwrap
 from random import shuffle
 import sys
 import textwrap
+import time
 
 from . import runner, progress
 
 
 from . import runner, progress
 
@@ -18,14 +19,20 @@ def panel_create(x, y):
     return (win, panel)
 
 
     return (win, panel)
 
 
+class CursesError(BaseException):
+    def __init__(self, message="lightcards: Curses error!"):
+        self.message = message
+        print(self.message)
+        sys.exit(3)
+
+
 class Help:
 class Help:
-    def __init__(self, outer):
+    def __init__(self, outer, mlines=21, mcols=52):
         """Initialize help screen"""
         self.outer = outer
         """Initialize help screen"""
         self.outer = outer
-        (self.win, self.panel) = panel_create(20, 52)
+        (self.win, self.panel) = panel_create(mlines, mcols)
         self.panel.top()
         self.panel.hide()
         self.panel.top()
         self.panel.hide()
-        self.win.clear()
 
         text = [
             "Welcome to runner. Here are some keybindings",
 
         text = [
             "Welcome to runner. Here are some keybindings",
@@ -40,6 +47,7 @@ class Help:
             "H, ?             open this screen",
             "e                open the input file in $EDITOR",
             "m                open the control menu",
             "H, ?             open this screen",
             "e                open the input file in $EDITOR",
             "m                open the control menu",
+            "1, 2, 3          switch views",
             "",
             "More information can be found in the man page, or",
             "by running `lightcards --help`.",
             "",
             "More information can be found in the man page, or",
             "by running `lightcards --help`.",
@@ -48,9 +56,12 @@ class Help:
         ]
 
         self.win.addstr(
         ]
 
         self.win.addstr(
-            1, 1, "LIGHTCARDS HELP", curses.color_pair(1) + curses.A_BOLD
+            1,
+            int(mcols / 2) - 8,
+            "LIGHTCARDS HELP",
+            curses.color_pair(1) + curses.A_BOLD,
         )
         )
-        self.win.hline(2, 1, curses.ACS_HLINE, 15)
+        self.win.hline(2, 1, curses.ACS_HLINE, mcols)
 
         for t in enumerate(text):
             self.win.addstr(t[0] + 3, 1, t[1])
 
         for t in enumerate(text):
             self.win.addstr(t[0] + 3, 1, t[1])
@@ -60,8 +71,7 @@ class Help:
     def disp(self):
         """Display help screen"""
         (mlines, mcols) = self.outer.win.getmaxyx()
     def disp(self):
         """Display help screen"""
         (mlines, mcols) = self.outer.win.getmaxyx()
-        self.win.mvwin(int(mlines / 2) - 10, int(mcols / 2) - 26)
-        self.outer.update_panels()
+        self.win.mvwin(int(mlines / 2) - 10, int(mcols / 2) - 25)
         self.panel.show()
 
         while True:
         self.panel.show()
 
         while True:
@@ -74,22 +84,24 @@ class Help:
 
 
 class Menu:
 
 
 class Menu:
-    def __init__(self, outer):
+    def __init__(self, outer, mlines=16, mcols=44):
         """Initialize the menu with content"""
         self.outer = outer
         """Initialize the menu with content"""
         self.outer = outer
-        (self.win, self.panel) = panel_create(17, 44)
+        (self.win, self.panel) = panel_create(mlines, mcols)
         self.panel.top()
         self.panel.hide()
 
         self.win.addstr(
         self.panel.top()
         self.panel.hide()
 
         self.win.addstr(
-            1, 1, "LIGHTCARDS MENU", curses.color_pair(1) + curses.A_BOLD
+            1,
+            int(mcols / 2) - 8,
+            "LIGHTCARDS MENU",
+            curses.color_pair(1) + curses.A_BOLD,
         )
         )
-        self.win.hline(2, 1, curses.ACS_HLINE, 15)
+        self.win.hline(2, 1, curses.ACS_HLINE, mcols)
         text = [
             "[y]: reset stack to original state",
             "[a]: alphabetize stack",
             "[z]: shuffle stack",
         text = [
             "[y]: reset stack to original state",
             "[a]: alphabetize stack",
             "[z]: shuffle stack",
-            "[f]: flip all cards in stack",
             "[t]: reverse stack order",
             "[u]: unstar all",
             "[d]: star all",
             "[t]: reverse stack order",
             "[u]: unstar all",
             "[d]: star all",
@@ -103,7 +115,6 @@ class Menu:
             self.win.addstr(t[0] + 3, 1, t[1])
 
         self.win.box()
             self.win.addstr(t[0] + 3, 1, t[1])
 
         self.win.box()
-        self.outer.update_panels()
 
     def menu_print(self, string, err=False):
         """Print messages on the menu screen"""
 
     def menu_print(self, string, err=False):
         """Print messages on the menu screen"""
@@ -112,11 +123,7 @@ class Menu:
         else:
             color = curses.color_pair(1)
 
         else:
             color = curses.color_pair(1)
 
-        for i in range(42):
-            self.win.addch(15, i + 1, " ")
-
-        self.win.addstr(15, 1, string, color)
-        self.outer.update_panels()
+        self.win.addstr(14, 1, string, color)
         self.menu_grab()
 
     def menu_grab(self):
         self.menu_grab()
 
     def menu_grab(self):
@@ -125,7 +132,6 @@ class Menu:
             key = self.win.getkey()
             if key in ["r", "m"]:
                 self.panel.hide()
             key = self.win.getkey()
             if key in ["r", "m"]:
                 self.panel.hide()
-                self.outer.update_panels()
                 self.outer.get_key()
             elif key == "q":
                 self.outer.leave()
                 self.outer.get_key()
             elif key == "q":
                 self.outer.leave()
@@ -133,7 +139,7 @@ class Menu:
                 self.outer.stack = runner.get_orig()[1]
                 self.menu_print("Stack reset!")
             elif key == "a":
                 self.outer.stack = runner.get_orig()[1]
                 self.menu_print("Stack reset!")
             elif key == "a":
-                self.outer.stack.sort()
+                self.outer.stack.sort(key=lambda x: x.front)
                 self.menu_print("Stack alphabetized!")
             elif key == "u":
                 [x.unStar() for x in self.outer.stack]
                 self.menu_print("Stack alphabetized!")
             elif key == "u":
                 [x.unStar() for x in self.outer.stack]
@@ -147,14 +153,6 @@ class Menu:
             elif key == "z":
                 shuffle(self.outer.stack)
                 self.menu_print("Stack shuffled!")
             elif key == "z":
                 shuffle(self.outer.stack)
                 self.menu_print("Stack shuffled!")
-            elif key == "f":
-                for x in self.outer.stack:
-                    x.front, x.back = x.back, x.front
-                (self.outer.headers[0], self.outer.headers[1]) = (
-                    self.outer.headers[1],
-                    self.outer.headers[0],
-                )
-                self.menu_print("Cards flipped!")
             elif key == "s":
                 # Check if there are any starred cards before proceeding, and
                 # if not, don't allow to proceed and show an error message
             elif key == "s":
                 # Check if there are any starred cards before proceeding, and
                 # if not, don't allow to proceed and show an error message
@@ -171,9 +169,6 @@ class Menu:
                     self.menu_print("Stars only!")
                 else:
                     self.menu_print("ERR: None are starred!", err=True)
                     self.menu_print("Stars only!")
                 else:
                     self.menu_print("ERR: None are starred!", err=True)
-            elif key in ["h", "KEY_LEFT"]:
-                self.outer.obj.index = len(self.outer.stack) - 1
-                self.outer.get_key()
             elif key == "r":
                 self.outer.obj.index = 0
                 self.outer.get_key()
             elif key == "r":
                 self.outer.obj.index = 0
                 self.outer.get_key()
@@ -183,40 +178,55 @@ class Menu:
         Display a menu offering multiple options on how to manipulate the deck
         and to continue
         """
         Display a menu offering multiple options on how to manipulate the deck
         and to continue
         """
+        for i in range(42):
+            self.win.addch(14, i + 1, " ")
+
         (mlines, mcols) = self.outer.win.getmaxyx()
         (mlines, mcols) = self.outer.win.getmaxyx()
-        self.win.mvwin(int(mlines / 2) - 9, int(mcols / 2) - 22)
+        self.win.mvwin(int(mlines / 2) - 8, int(mcols / 2) - 22)
         self.panel.show()
         self.panel.show()
-        self.outer.update_panels()
 
         self.menu_grab()
 
 
 class Display:
 
         self.menu_grab()
 
 
 class Display:
-    def __init__(self, stack, headers, obj):
+    def __init__(self, stack, headers, obj, view):
         self.stack = stack
         self.headers = headers
         self.obj = obj
         self.stack = stack
         self.headers = headers
         self.obj = obj
+        self.view = view
 
     def run(self, stdscr):
         """Set important options that require stdscr before starting"""
         self.win = stdscr
 
     def run(self, stdscr):
         """Set important options that require stdscr before starting"""
         self.win = stdscr
-        (mlines, mcols) = self.win.getmaxyx()
         curses.curs_set(0)  # Hide cursor
         curses.use_default_colors()  # Allow transparency
         curses.init_pair(1, curses.COLOR_CYAN, -1)
         curses.init_pair(2, curses.COLOR_RED, -1)
         curses.init_pair(3, curses.COLOR_YELLOW, -1)
 
         curses.curs_set(0)  # Hide cursor
         curses.use_default_colors()  # Allow transparency
         curses.init_pair(1, curses.COLOR_CYAN, -1)
         curses.init_pair(2, curses.COLOR_RED, -1)
         curses.init_pair(3, curses.COLOR_YELLOW, -1)
 
-        (self.main_win, self.main_panel) = panel_create(mlines, mcols)
+        self.main_panel = curses.panel.new_panel(self.win)
         self.menu_obj = Menu(self)
         self.help_obj = Help(self)
 
         self.get_key()
 
         self.menu_obj = Menu(self)
         self.help_obj = Help(self)
 
         self.get_key()
 
-    def update_panels(self):
-        """Update panel and window contents"""
-        curses.panel.update_panels()
-        self.win.refresh()
+    def check_size(self):
+        (mlines, mcols) = self.win.getmaxyx()
+
+        while mlines < 24 or mcols < 60:
+            self.win.clear()
+            self.win.addstr(
+                0,
+                0,
+                textwrap.fill(
+                    "Terminal too small! Min size 60x24", width=mcols
+                ),
+            )
+            self.win.refresh()
+            (mlines, mcols) = self.win.getmaxyx()
+            time.sleep(0.1)
+        else:
+            self.disp_card()
 
     def leave(self):
         """Pickle stack before quitting"""
 
     def leave(self):
         """Pickle stack before quitting"""
@@ -235,7 +245,8 @@ class Display:
         Display the statusbar at the bottom of the screen with progress, star
         status, and card side.
         """
         Display the statusbar at the bottom of the screen with progress, star
         status, and card side.
         """
-        (mlines, _) = self.win.getmaxyx()
+        (mlines, mcols) = self.win.getmaxyx()
+        self.win.hline(mlines - 2, 0, 0, mcols)
 
         # Calculate percent done
         if len(self.stack) <= 1:
 
         # Calculate percent done
         if len(self.stack) <= 1:
@@ -251,17 +262,21 @@ class Display:
         else:
             star_color = curses.color_pair(1)
 
         else:
             star_color = curses.color_pair(1)
 
-        # Create bar component
+        # Compose bar text
         bar_start = "["
         bar_middle = self.current_card().printStar()
         bar_end = (
             f"] [{len(self.nstarred())}/{str(len(self.stack))} starred] "
             f"[{percent}% ("
             f"{str(self.obj.index).zfill(len(str(len(self.stack))))}"
         bar_start = "["
         bar_middle = self.current_card().printStar()
         bar_end = (
             f"] [{len(self.nstarred())}/{str(len(self.stack))} starred] "
             f"[{percent}% ("
             f"{str(self.obj.index).zfill(len(str(len(self.stack))))}"
-            f"/{str(len(self.stack))})] ["
-            f"{self.headers[self.current_card().side]} ("
-            f"{str(int(self.current_card().side) + 1)})]"
+            f"/{str(len(self.stack))})]"
         )
         )
+        if self.view != 3:
+            bar_end += (
+                f" [{self.get_side()} ("
+                f"{str(int(self.current_card().side) + 1)})]"
+            )
+        bar_end += f" [View {str(self.view)}]"
 
         # Put it all togethor
         self.win.addstr(mlines - 1, 0, bar_start, curses.color_pair(1))
 
         # Put it all togethor
         self.win.addstr(mlines - 1, 0, bar_start, curses.color_pair(1))
@@ -269,7 +284,7 @@ class Display:
         self.win.addstr(
             mlines - 1,
             len(bar_start + bar_middle),
         self.win.addstr(
             mlines - 1,
             len(bar_start + bar_middle),
-            bar_end,
+            textwrap.shorten(bar_end, width=mcols - 20, placeholder="…"),
             curses.color_pair(1),
         )
 
             curses.color_pair(1),
         )
 
@@ -281,59 +296,97 @@ class Display:
             wrap_width = 80
         return wrap_width
 
             wrap_width = 80
         return wrap_width
 
+    def get_side(self):
+        if self.obj.side == 0:
+            return self.headers[self.current_card().side]
+        else:
+            return self.headers[self.current_card().get_reverse()]
+
     def disp_card(self):
     def disp_card(self):
-        """
-        Display the contents of the card.
-        Shows a header, a horizontal line, and the contents of the current
-        side.
-        """
-        (mlines, mcols) = self.win.getmaxyx()
+        (_, mcols) = self.win.getmaxyx()
         self.main_panel.bottom()
         self.main_panel.bottom()
-        self.main_win.clear()
-        # If on the back of the card, show the content of the front side in
-        # the header
+        self.win.clear()
         num_done = str(self.obj.index + 1).zfill(len(str(len(self.stack))))
         num_done = str(self.obj.index + 1).zfill(len(str(len(self.stack))))
-        if self.current_card().side == 0:
-            top = num_done + " | " + self.headers[self.current_card().side]
-        else:
-            top = (
-                num_done
-                + " | "
-                + self.headers[self.current_card().side]
-                + ' | "'
-                + str(self.current_card().front)
-                + '"'
+
+        if self.view in [1, 2]:
+            """
+            Display the contents of the card.
+            Shows a header, a horizontal line, and the contents of the current
+            side.
+            """
+            # If on the back of the card, show the content of the front side in
+            # the header
+            if self.view == 1:
+                self.obj.side = 0
+            elif self.view == 2:
+                self.obj.side = 1
+
+            if self.current_card().side == 0:
+                top = num_done + " | " + self.get_side()
+            else:
+                top = (
+                    num_done
+                    + " | "
+                    + self.get_side()
+                    + ' | "'
+                    + str(self.current_card().get()[self.obj.get_reverse()])
+                    + '"'
+                )
+
+            self.win.addstr(
+                0,
+                0,
+                textwrap.shorten(top, width=mcols - 20, placeholder="…"),
+                curses.A_BOLD,
             )
             )
-        header_width = mcols
-        if mcols > 80:
-            header_width = 80
 
 
-        self.main_win.addstr(
-            0,
-            0,
-            textwrap.shorten(top, width=header_width, placeholder="…"),
-            curses.A_BOLD,
-        )
+            # Show current side
+            self.win.addstr(
+                2,
+                0,
+                textwrap.fill(
+                    self.current_card().get()[self.obj.side],
+                    width=self.wrap_width(),
+                ),
+            )
 
 
-        # Add horizontal line
-        lin_width = header_width
-        if len(top) < header_width:
-            lin_width = len(top)
-        self.main_win.hline(1, 0, curses.ACS_HLINE, lin_width)
+        elif self.view == 3:
+            """
+            Display the contents of the card with both the front and back sides.
+            """
+            (_, mcols) = self.win.getmaxyx()
+            self.main_panel.bottom()
+            self.win.clear()
+
+            self.win.addstr(
+                0,
+                0,
+                textwrap.shorten(
+                    num_done,
+                    width=mcols - 20,
+                    placeholder="…",
+                ),
+                curses.A_BOLD,
+            )
 
 
-        # Show current side
-        self.main_win.addstr(
-            2,
-            0,
-            textwrap.fill(
-                self.current_card().get(),
-                width=self.wrap_width(),
-            ),
-        )
-        self.update_panels()
+            # Show card content
+            self.win.addstr(
+                2,
+                0,
+                textwrap.fill(
+                    self.headers[0] + ": " + self.current_card().front,
+                    width=self.wrap_width(),
+                )
+                + "\n\n"
+                + textwrap.fill(
+                    self.headers[1] + ": " + self.current_card().back,
+                    width=self.wrap_width(),
+                ),
+            )
+
+        self.win.hline(1, 0, curses.ACS_HLINE, mcols)
         self.disp_bar()
         self.disp_sidebar()
         self.disp_bar()
         self.disp_sidebar()
-        self.win.hline(mlines - 2, 0, 0, mcols)
 
     def current_card(self):
         """Get current card object"""
 
     def current_card(self):
         """Get current card object"""
@@ -344,8 +397,8 @@ class Display:
         Display a card and wait for the input.
         Used as a general way of getting back into the card flow from a menu
         """
         Display a card and wait for the input.
         Used as a general way of getting back into the card flow from a menu
         """
-        self.disp_card()
         while True:
         while True:
+            self.check_size()
             key = self.win.getkey()
             if key == "q":
                 self.leave()
             key = self.win.getkey()
             if key == "q":
                 self.leave()
@@ -360,7 +413,7 @@ class Display:
                     self.obj.forward(self.stack)
                     self.current_card().side = 0
                     self.disp_card()
                     self.obj.forward(self.stack)
                     self.current_card().side = 0
                     self.disp_card()
-            elif key in ["j", "k", "KEY_UP", "KEY_DOWN"]:
+            elif key in ["j", "k", "KEY_UP", "KEY_DOWN"] and self.view != 3:
                 self.current_card().flip()
                 self.disp_card()
             elif key in ["i", "/"]:
                 self.current_card().flip()
                 self.disp_card()
             elif key in ["i", "/"]:
@@ -381,6 +434,8 @@ class Display:
             elif key == "e":
                 (self.headers, self.stack) = runner.reparse()
                 self.get_key()
             elif key == "e":
                 (self.headers, self.stack) = runner.reparse()
                 self.get_key()
+            elif key in ["1", "2", "3"]:
+                self.view = int(key)
 
     def disp_sidebar(self):
         """Display a sidebar with the starred terms"""
 
     def disp_sidebar(self):
         """Display a sidebar with the starred terms"""
@@ -394,27 +449,24 @@ class Display:
             curses.color_pair(3) + curses.A_BOLD,
         )
         self.win.vline(0, mcols - 20, 0, mlines - 2)
             curses.color_pair(3) + curses.A_BOLD,
         )
         self.win.vline(0, mcols - 20, 0, mlines - 2)
-        self.win.hline(1, left, 0, mlines)
 
         nstarred = self.nstarred()
 
         nstarred = self.nstarred()
-        if mlines - 5 < len(self.nstarred()):
-            nstarred = self.nstarred()[: mlines - 4]
-        elif mlines - 5 == len(self.nstarred()):
-            nstarred = self.nstarred()[: mlines - 3]
-
-        for _ in nstarred:
-            for i, card in enumerate(nstarred):
-                term = card.front
-                if len(term) > 18:
-                    term = term + "…"
-                self.win.addstr(2 + i, left, term)
-            if not nstarred == self.nstarred():
+        for i, card in enumerate(nstarred):
+            term = card.get()[self.obj.side]
+            if len(term) > 18:
+                term = term[:18] + "…"
+
+            if i > mlines - 5:
+                for i in range(19):
+                    self.win.addch(mlines - 3, left + i, " ")
+
                 self.win.addstr(
                     mlines - 3,
                     left,
                 self.win.addstr(
                     mlines - 3,
                     left,
-                    f"({len(self.nstarred()) - len(nstarred)} more)",
+                    f"({len(nstarred) - i - 2} more)",
                 )
                 )
-                break
+            else:
+                self.win.addstr(2 + i, left, term)
 
         if len(self.nstarred()) == 0:
             self.win.addstr(2, left, "None starred")
 
         if len(self.nstarred()) == 0:
             self.win.addstr(2, left, "None starred")