]> git.armaanb.net Git - lightcards.git/blobdiff - lightcards/display.py
Move percent done from right to left
[lightcards.git] / lightcards / display.py
index 11a7a91d26cb1afdfed1b1c3800101743a00c79f..50b741808b14dc0ed18ed417c528322ac83cb2f1 100644 (file)
@@ -3,11 +3,13 @@
 
 import curses
 import curses.panel
 
 import curses
 import curses.panel
+import os
 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, parse
 
 
 def panel_create(x, y):
 
 
 def panel_create(x, y):
@@ -18,14 +20,53 @@ 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 Quit:
+    def __init__(self, outer, mlines=5, mcols=20):
+        self.outer = outer
+        (self.win, self.panel) = panel_create(mlines, mcols)
+        self.panel.top()
+        self.panel.hide()
+
+        self.win.addstr(
+            1,
+            2,
+            "QUIT LIGHTCARDS?",
+            curses.color_pair(1) + curses.A_BOLD,
+        )
+        self.win.hline(2, 1, curses.ACS_HLINE, mcols)
+        self.win.addstr(3, 1, "Quit? [y/n]")
+
+        self.win.box()
+
+    def disp(self):
+        """Display quit confirmation screen"""
+        (mlines, mcols) = self.outer.win.getmaxyx()
+        self.win.mvwin(int(mlines / 2) - 3, int(mcols / 2) - 10)
+        self.panel.show()
+
+        while True:
+            key = self.win.getkey()
+            if key == "y":
+                break
+            elif key == "n":
+                self.panel.hide()
+                self.outer.get_key()
+
+
 class Help:
 class Help:
-    def __init__(self, outer):
+    def __init__(self, outer, mlines=20, 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",
@@ -38,8 +79,8 @@ class Help:
             "0, ^, home       go to the start of the deck",
             "$, end           go to the end of the deck",
             "H, ?             open this screen",
             "0, ^, home       go to the start of the deck",
             "$, end           go to the end of the deck",
             "H, ?             open this screen",
-            "e                open the input file in $EDITOR",
             "m                open the control menu",
             "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 +89,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 +104,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,26 +117,29 @@ class Help:
 
 
 class Menu:
 
 
 class Menu:
-    def __init__(self, outer):
+    def __init__(self, outer, mlines=17, 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",
             "[s]: update stack to include starred only",
             "[t]: reverse stack order",
             "[u]: unstar all",
             "[d]: star all",
             "[s]: update stack to include starred only",
+            "[e]: open the input file in $EDITOR",
             "",
             "[r]: restart",
             "[m]: close menu",
             "",
             "[r]: restart",
             "[m]: close menu",
@@ -103,7 +149,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 +157,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.win.addstr(15, 1, string, color)
-        self.outer.update_panels()
         self.menu_grab()
 
     def menu_grab(self):
         self.menu_grab()
 
     def menu_grab(self):
@@ -125,7 +166,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()
@@ -147,14 +187,13 @@ 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],
+            elif key == "e":
+                curses.endwin()
+                os.system(f"$EDITOR {self.outer.input_file}"),
+                (self.outer.headers, self.outer.stack) = parse.parse_html(
+                    parse.md2html(self.outer.input_file)
                 )
                 )
-                self.menu_print("Cards flipped!")
+                self.outer.get_key()
             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 +210,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,43 +219,61 @@ 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, input_file):
         self.stack = stack
         self.headers = headers
         self.obj = obj
         self.stack = stack
         self.headers = headers
         self.obj = obj
+        self.view = view
+        self.input_file = input_file
 
     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.menu_obj = Menu(self)
         self.help_obj = Help(self)
+        self.quit_obj = Quit(self)
 
         self.get_key()
 
 
         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):
 
     def leave(self):
-        """Pickle stack before quitting"""
+        """Pickle stack and confirm before quitting"""
+        self.quit_obj.disp()
         if self.obj.index + 1 == len(self.stack):
             self.obj.index = 0
 
         if self.obj.index + 1 == len(self.stack):
             self.obj.index = 0
 
@@ -235,7 +289,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 - 3, 0, 0, mcols)
 
         # Calculate percent done
         if len(self.stack) <= 1:
 
         # Calculate percent done
         if len(self.stack) <= 1:
@@ -251,28 +306,55 @@ 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_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)})]"
-        )
+        bar_end = f"] [{len(self.nstarred())}/{str(len(self.stack))} starred] "
+        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
 
         # Put it all togethor
-        self.win.addstr(mlines - 1, 0, bar_start, curses.color_pair(1))
-        self.win.addstr(mlines - 1, len(bar_start), bar_middle, star_color)
+        height = mlines - 2
+        self.win.addstr(height, 0, bar_start, curses.color_pair(1))
+        self.win.addstr(height, len(bar_start), bar_middle, star_color)
         self.win.addstr(
         self.win.addstr(
-            mlines - 1,
+            height,
             len(bar_start + bar_middle),
             len(bar_start + bar_middle),
-            bar_end,
+            textwrap.shorten(bar_end, width=mcols - 20, placeholder="…"),
+            curses.color_pair(1),
+        )
+
+        progress = (
+            f"[{percent}% ("
+            f"{str(self.obj.index + 1).zfill(len(str(len(self.stack))))}"
+            f"/{str(len(self.stack))})] "
+        )
+
+        self.win.addstr(
+            height + 1,
+            0,
+            progress,
             curses.color_pair(1),
         )
 
             curses.color_pair(1),
         )
 
+        for i in range(
+            int(
+                self.obj.index
+                / (len(self.stack) - 1)
+                * (mcols - len(progress))
+                - 1
+            )
+        ):
+            # TODO: Use the variying width unicode block characters to make a
+            # super accurate bar
+            self.win.addch(
+                height + 1, i + len(progress), "»", curses.color_pair(1)
+            )
+
     def wrap_width(self):
         """Calculate the width at which the body text should wrap"""
         (_, mcols) = self.win.getmaxyx()
     def wrap_width(self):
         """Calculate the width at which the body text should wrap"""
         (_, mcols) = self.win.getmaxyx()
@@ -281,59 +363,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, 4]:
+            """
+            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 +464,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 +480,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", "/"]:
@@ -378,9 +498,8 @@ class Display:
                 self.help_obj.disp()
             elif key == "m":
                 self.menu_obj.disp()
                 self.help_obj.disp()
             elif key == "m":
                 self.menu_obj.disp()
-            elif key == "e":
-                (self.headers, self.stack) = runner.reparse()
-                self.get_key()
+            elif key in ["1", "2", "3", "4"]:
+                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"""
@@ -393,28 +512,25 @@ class Display:
             "STARRED CARDS",
             curses.color_pair(3) + curses.A_BOLD,
         )
             "STARRED CARDS",
             curses.color_pair(3) + curses.A_BOLD,
         )
-        self.win.vline(0, mcols - 20, 0, mlines - 2)
-        self.win.hline(1, left, 0, mlines)
+        self.win.vline(0, mcols - 20, 0, mlines - 3)
 
         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 - 6:
+                for i in range(19):
+                    self.win.addch(mlines - 3, left + i, " ")
+
                 self.win.addstr(
                 self.win.addstr(
-                    mlines - 3,
+                    mlines - 4,
                     left,
                     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")