diff --git a/README.md b/README.md index 59b9d5a..062d382 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ NoteVelocity =================== -A speedy note-taking program +A speedy note-taking and revision program --------------------- -This is an EPQ (Extended Project Qualification) project for designing and making a note-taking program for university and A-level students. The main goals will be speed of operation and effectiveness of using NoteVelocity for revision. +This is an EPQ (Extended Project Qualification) project for designing and making a note-taking program for A-level students to easily revise from. The main goals will be speed of operation and effectiveness of using NoteVelocity for revision. Made using *Python* and *Tkinter*. **Dependencies:** -* *Python* >= *3.3*, as of commit 47 +* *Python* >= *3.3* * *Tkinter* +* *Source Sans Pro* diff --git a/notevelocity/bindings.py b/notevelocity/bindings.py index e1db0d4..db242bc 100644 --- a/notevelocity/bindings.py +++ b/notevelocity/bindings.py @@ -21,14 +21,21 @@ rename = "" openFile = "" -quit = "" +nextTab = "" +prevTab = "" +newTab = "" +newTabTwo = "" +closeTab = "" + +quit = "" decreaseIndent = "" increaseIndent = "" # Or the tab key when at the start of a line -margins = 2 +makeHeading = "" +makeSubheading = "" -# Main +# Binding (Do not edit) def init(self, root): print("bindings initialised") @@ -40,5 +47,15 @@ def init(self, root): # Opening root.bind(openFile, lambda event: self.openFile()) + # Tab control + root.bind(nextTab, lambda event: self.tabBar.switch(0, 1)) + root.bind(prevTab, lambda event: self.tabBar.switch(0, -1)) + root.bind(newTab, lambda event: self.tabBar.add(self, "New Note")) + root.bind(newTabTwo, lambda event: self.tabBar.add(self, "New Note")) + root.bind(closeTab, lambda event: self.tabBar.closeCurrent()) + + root.bind(makeHeading, lambda event: print("Making heading")) + root.bind(makeSubheading, lambda event: print("Making subheading")) + # Quit root.bind(quit, lambda event: self.quit()) \ No newline at end of file diff --git a/notevelocity/frames.py b/notevelocity/frames.py index a902ba4..38eca37 100644 --- a/notevelocity/frames.py +++ b/notevelocity/frames.py @@ -15,6 +15,7 @@ from tkinter import * from tkinter.ttk import * import bindings +import styles ## Main # Title bar @@ -26,19 +27,19 @@ def __init__(self, master, root): self.testMessage = "titleBar is initialised" - self.Frame = Frame() + self.Frame = Frame(style = "TB.TFrame") self.Frame.pack(fill = X, side = TOP, expand = 0, ipadx = 2, ipady = 2) self.icon = Frame(self.Frame) self.icon.pack(expand = 0, side = LEFT) - self.buttonA = Button(self.Frame, text = "Save") + self.buttonA = Button(self.Frame, text = "Save", style = "TB.TButton") self.buttonA.pack(expand = 0, side = LEFT) - self.buttonB = Button(self.Frame, text = "Open") + self.buttonB = Button(self.Frame, text = "Open", style = "TB.TButton") self.buttonB.pack(expand = 0, side = LEFT) - self.title = Label(self.Frame, text = " New note", anchor = "w") + self.title = Label(self.Frame, text = " New note", anchor = "w", style = "T.TLabel") self.title.pack(expand = 1, fill = BOTH, side = LEFT) self.Bindings() @@ -72,17 +73,28 @@ def __init__(self, master): self.testMessage = "formatBar is initialised" - self.Frame = Frame() + self.Frame = Frame(style = "TB.TFrame") self.Frame.pack(fill = Y, side = LEFT, expand = 0, ipadx = 2, ipady = 2) - self.bold = Button(self.Frame, text = "B") - self.bold.pack(expand = 0, side = TOP) + self.spacer1 = Frame(self.Frame, height = 2) + self.spacer1.pack(expand = 0, side = TOP) + + self.title = Button(self.Frame, text = "T", style = "F.TButton") + self.title.pack(expand = 0, side = TOP) + + self.subTitle = Button(self.Frame, text = "S", style = "F.TButton") + self.subTitle.pack(expand = 0, side = TOP) - self.italic = Button(self.Frame, text = "I") - self.italic.pack(expand = 0, side = TOP) + self.notes = Button(self.Frame, text = "N", style = "F.TButton") - self.settings = Button(self.Frame, text = "S") - self.settings.pack(expand = 0, side = BOTTOM) + self.spacer2 = Frame(self.Frame, height = 5) + self.spacer2.pack(expand = 0, side = TOP) + + self.equation = Button(self.Frame, text = "E", style = "F.TButton") + self.equation.pack(expand = 0, side = TOP) + + self.settings = Button(self.Frame, text = "Set", style = "F.TButton") + self.settings.pack(expand = 0, side = BOTTOM, padx = 2, pady = 4) # Text Frame class text(Frame): @@ -93,15 +105,16 @@ def __init__(self, master, root): self.testMessage = "textFrame is initialised" - self.Frame = Frame() - self.Frame.pack(fill = BOTH, expand = 1, side = LEFT) + self.Frame = Frame(style = "TB.TFrame") + self.Frame.pack(fill = BOTH, expand = 1, side = TOP) self.scrollbar = Scrollbar(self.Frame) self.scrollbar.pack(expand = 0, fill = Y, side = RIGHT) self.textBox = Text(self.Frame) self.textBox.pack(expand = 1, fill = BOTH, side = LEFT) - self.textBox.config(tabs = ("0.5c", "0.75c", "0.825c")) + self.textBox.config(tabs = ("0.5c", "0.75c", "0.825c"), borderwidth = 0) + self.textBox.config(bg = master.master.textBoxBackground, fg = master.master.textBoxTextColour) # Link self.textBox and self.scrollbar self.textBox.config(yscrollcommand = self.scrollbar.set) @@ -237,4 +250,14 @@ def updateTags(self): self.root.after(1000, self.updateTags) else: - print("Tag updating disabled") \ No newline at end of file + print("Tag updating disabled") + +class arrangementFrame(Frame): + def __init__(self, master): + self.master = master + + self.testMessage = "arrangementFrame is initialised" + + self.Frame = Frame() + self.Frame.pack(side = LEFT) + diff --git a/notevelocity/main.py b/notevelocity/main.py index 8f7bb49..c674d6e 100644 --- a/notevelocity/main.py +++ b/notevelocity/main.py @@ -17,9 +17,12 @@ from tkinter import messagebox from os import getcwd from os import remove +import platform import frames +import tabs import bindings import logging +import styles ## Main loop class AppFrame(Frame): @@ -28,6 +31,7 @@ def __init__(self, master): self.log = logging.Log(self) + self.osStuff() self.initVars() self.initUI() @@ -39,38 +43,60 @@ def initVars(self): def initUI(self): self.log.write("Initialising UI") + styles.init(self) + ## Top level frames try: self.titleBar = frames.titleBar(self, root) self.log.write(self.titleBar.testMessage) - except: + except Exception as ex: print("titleBar was not initialised properly") - self.log.writeError("titleBar was not initialised properly") + self.log.writeError("titleBar was not initialised properly. Error:\n" + str(ex)) self.quit() try: self.formatBar = frames.formatBar(self) self.log.write(self.formatBar.testMessage) - except: + except Exception as ex: print("formatBar was not initialised properly") - self.log.writeError("formatBar was not initialised properly") + self.log.writeError("formatBar was not initialised properly. Error:\n" + str(ex)) self.quit() try: - self.textFrame = frames.text(self, root) + self.arrangementFrame = frames.arrangementFrame(self) + self.log.write(self.arrangementFrame.testMessage) + except Exception as ex: + print("arrangementFrame was not initialised properly") + self.log.writeError("arrangementFrame was not initialised properly. Error:\n" + str(ex)) + self.quit() + + try: + self.textFrame = frames.text(self.arrangementFrame, root) self.log.write(self.textFrame.testMessage) - except: + except Exception as ex: print("textFrame was not initialised properly") - self.log.writeError("textFrame was not initialised properly") + self.log.writeError("textFrame was not initialised properly. Error:\n" + str(ex)) self.quit() + """try: + self.tabBar = tabs.tabBar(self.arrangementFrame) + self.log.write(self.tabBar.testMessage) + except Exception as ex: + print("tabBar was not initialised properly") + self.log.writeError("tabBar was not initialsed properly. Error:\n" + str(ex)) + self.quit()""" + + self.tabBar = tabs.tabBar(self.arrangementFrame) + try: bindings.init(self, root) - except: + except Exception as ex: print("bindings were not initialised properly") - self.log.writeError("bindings were not initialised properly") + self.log.writeError("bindings were not initialised properly. Error:\n" + str(ex)) self.quit() + self.textFrame.textBox.focus_set() + self.log.write("All initialised\n") def saveFile(self, mode): @@ -96,6 +122,7 @@ def saveFile(self, mode): fileToSave.close() self.textFrame.changed = False + self.tabBar.resetChanged() elif mode == 2: # Save file as @@ -103,6 +130,7 @@ def saveFile(self, mode): saveLocation = filedialog.asksaveasfilename(initialdir = self.notesDir, title = "Save note as", filetypes = [("Note files", "*.note")]) self.textFrame.fileName = saveLocation + self.tabBar.updateFilename() if saveLocation is None or saveLocation is False or saveLocation is "" or saveLocation is "\n" or saveLocation == (): print("No save location selected. Cancelling") @@ -118,11 +146,14 @@ def saveFile(self, mode): self.log.write("Saved file at " + saveLocation) self.textFrame.fileName = saveLocation - self.titleBar.title.config(text = " " + saveLocation.split("/")[-1].split(".")[-2]) + shortName = saveLocation.split(self.slashChar)[-1].split(".")[-2] + self.titleBar.title.config(text = " " + shortName) + self.tabBar.renameCurrent(shortName) fileToSave.close() self.textFrame.changed = False + self.tabBar.resetChanged() elif mode == 3: # Rename @@ -141,39 +172,37 @@ def saveFile(self, mode): print("Renamed file to " + saveLocation) self.log.write("Renamed file to " + saveLocation) - remove(self.textFrame.fileName) - print(self.textFrame.fileName) + if self.textFrame.fileName != "": + remove(self.textFrame.fileName) self.textFrame.fileName = saveLocation - print(self.textFrame.fileName) - self.titleBar.title.config(text = " " + saveLocation.split("/")[-1].split(".")[-2]) + shortName = saveLocation.split(self.slashChar)[-1].split(".")[-2] + self.titleBar.title.config(text = " " + shortName) + self.tabBar.renameCurrent(shortName) fileToSave.close() self.textFrame.changed = False + self.tabBar.resetChanged() else: print("saveFile index out of range") self.log.write("saveFile index out of range") def openFile(self): - # Check whether textbox contents have changed. If yes, ask to save. - if self.textFrame.changed == True: - yesno = messagebox.askyesno("Save current file?", "The currently open file has not been saved. Save it?") - - if yesno: - self.saveFile(1) - else: - self.textFrame.changed = False - openLocation = filedialog.askopenfilename(initialdir = self.notesDir, title = "Select note to open", filetypes = [("Note files", "*.note")]) if openLocation is None or openLocation is "" or openLocation is "\n" or openLocation is False or openLocation == (): print("No open location selected") self.log.write("No open location selected") - elif self.textFrame.changed == False: + else: openFile = open(openLocation, "r") + self.textFrame.fileName = openLocation + shortName = self.textFrame.fileName.split(self.slashChar)[-1].split(".")[-2] + self.tabBar.add(self.tabBar, shortName) + self.titleBar.title.config(text = " " + shortName) + self.textFrame.textBox.delete("1.0", "end") lineNum = 1 @@ -184,16 +213,10 @@ def openFile(self): lineNum += 1 - self.textFrame.fileName = openLocation - self.titleBar.title.config(text = " " + self.textFrame.fileName.split("/")[-1].split(".")[-2]) - - print("Opening file from " + openLocation) - self.log.write("Opening file from " + openLocation) + print("Opened file from " + openLocation) + self.log.write("Opened file from " + openLocation) self.textFrame.changed = False - else: - print("Error opening file. The \'changed\' variable may not have been correctly set.") - self.log.writeError("Error opening file. The \'changed\' variable may not have been correctly set.") def max(self): pass @@ -216,8 +239,19 @@ def log(self, mode): elif mode == 1: pass - def initStyles(self): - s = Style() + def osStuff(self): + self.os = platform.system() + + if self.os == "Windows": + self.slashChar = "\\" + elif self.os == "Linux": + self.slashChar = "/" + elif self.os == "Darwin": + self.slashChar = "/" + else: + self.log.wrteError("System not detected as Windows, Mac or Linux. Some features may not work.") + print("System not detected as Windows, Mac or Linux. Some features may not work.") + self.slashChar = "/" # Set root window properties root = Tk() diff --git a/notevelocity/styles.py b/notevelocity/styles.py new file mode 100644 index 0000000..f5269d6 --- /dev/null +++ b/notevelocity/styles.py @@ -0,0 +1,65 @@ +""" + +NoteVelocity - A speedy note-taking program +Copyright (C) 2013 George Bryant + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see . + +""" + +from tkinter import * +from tkinter.ttk import * + +def init(master): + # Fonts + buttonFont = ("Source Sans Pro", 10) + smallButtonFont = ("Source Sans Pro", 8) + titleFont = ("Source Sans Pro", 11, "bold") + tabFont = ("Source Sans Pro", 10) + + # Colours - these values should be in hexadecimal + toolbarColour = "#E8E8E8" + toolbarFontColour = "#303030" + toolbarButtonColour = "#FFFFFF" + + tabColour = "#DDDDDD" + tabSelectedColour = "#B8B8B8" + + textBoxBackground = "#FCFCFC" + textBoxTextColour = "#101010" + + # Styles (Do not edit) + titleBarStyle = Style() + titleBarStyle.configure("TB.TFrame", background = toolbarColour) + + titleBarButtonStyle = Style() + titleBarButtonStyle.configure("TB.TButton", foreground = toolbarFontColour, background = toolbarButtonColour, width = 7, font = buttonFont) + + formatButtonStyle = Style() + formatButtonStyle.configure("F.TButton", foreground = toolbarFontColour, background = toolbarButtonColour, width = 3, font = buttonFont) + + titleStyle = Style() + titleStyle.configure("T.TLabel", foreground = toolbarFontColour, font = titleFont) + + tabBarStyle = Style() + tabBarStyle.configure("TabBar.TFrame", background = toolbarColour) + + tabStyle = Style() + tabStyle.configure("Tab.TFrame", background = tabColour) + tabSelectedStyle = Style() + tabSelectedStyle.configure("TabSelected.TFrame", background = tabSelectedColour) + + tabButtonStyle = Style() + tabButtonStyle.configure("Tab.TButton", foreground = toolbarFontColour, background = toolbarButtonColour, font = smallButtonFont) + + tabTitleStyle = Style() + tabTitleStyle.configure("TT.TLabel", foreground = toolbarFontColour, background = tabColour, font = tabFont) + tabTitleSelectedStyle = Style() + tabTitleSelectedStyle.configure("TTS.TLabel", foreground = toolbarFontColour, background = tabSelectedColour, font = tabFont) + + master.textBoxBackground = textBoxBackground + master.textBoxTextColour = textBoxTextColour \ No newline at end of file diff --git a/notevelocity/tabs.py b/notevelocity/tabs.py new file mode 100644 index 0000000..bc61b9d --- /dev/null +++ b/notevelocity/tabs.py @@ -0,0 +1,220 @@ +""" + +NoteVelocity - A speedy note-taking program +Copyright (C) 2013 George Bryant + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see . + +""" + +# Imports +from tkinter import * +from tkinter.ttk import * +import bindings + +class tabBar(Frame): + def __init__(self, master): + self.master = master + + self.testMessage = "tabBar is initialised" + + self.Frame = Frame(height = 24, style = "TabBar.TFrame") + self.Frame.pack(fill = X, expand = 0, side = BOTTOM) + + self.tabs = list() + self.blank = StringVar() + self.blank.set("") + + self.lastSelectedTab = 0 + self.selectedTab = 0 + + self.add(self, "New Note") + + def add(self, master, title): + newTabNum = len(self.tabs) + self.tabs.append(self.tab(self, title)) + + self.switch(1, len(self.tabs)-1) + + def switch(self, mode, amount): + self.lastSelectedTab = self.selectedTab + + if mode == 0: #Add amount + if self.selectedTab + amount < len(self.tabs) and self.selectedTab + amount >= 0: + self.selectedTab += amount + + elif self.selectedTab + amount >= len(self.tabs) - 1: + self.selectedTab = 0 + + elif self.selectedTab + amount < 0: + self.selectedTab = len(self.tabs) - 1 + + elif mode == 1: # Switch to amount + if amount < len(self.tabs) and amount >= 0: + self.selectedTab = amount + + elif amount < 0: + self.selectedTab = 0 + + elif amount >= len(self.tabs): + self.selectedTab = len(self.tabs) - 1 + + self.tabs[self.lastSelectedTab].deselect() + self.tabs[self.selectedTab].select() + self.swapBoxContents(self.lastSelectedTab, self.selectedTab) + + def swapBoxContents(self, last, new): + textBoxContents = self.master.master.textFrame.textBox.get(1.0, "end") + textBoxContents = textBoxContents[:-1] + self.tabs[last].text.set(textBoxContents) + + self.master.master.textFrame.textBox.delete(1.0, "end") + textBoxContents = self.tabs[new].text.get() + self.master.master.textFrame.textBox.insert(1.0, textBoxContents) + + self.master.master.titleBar.title.config(text = self.tabs[self.selectedTab].longTitle) + + # Update textBox properties + self.tabs[last].fileName = self.master.master.textFrame.fileName + self.master.master.textFrame.fileName = self.tabs[new].fileName + + def updateFilename(self): + self.tabs[self.selectedTab].fileName = self.master.master.textFrame.fileName + print(self.master.master.textFrame.fileName) + + def closeCurrent(self): + if len(self.tabs) > 1: + self.switch(1, self.selectedTab - 1) + + self.tabs[self.lastSelectedTab].close() + del self.tabs[self.lastSelectedTab] + else: + self.lastSelectedTab = 0 + self.selectedTab = 0 + + self.tabs[self.lastSelectedTab].close() + del self.tabs[0] + + self.add(self, "New Note") + self.master.master.textFrame.textBox.delete(1.0, "end") + self.master.master.textFrame.textBox.insert(1.0, self.blank.get()) + + def closeSpecific(self, tabNum): + if tabNum < self.selectedTab: + self.tabs[tabNum].close() + del self.tabs[tabNum] + self.selectedTab -= 1 + + elif tabNum > self.selectedTab: + self.tabs[tabNum].close() + del self.tabs[tabNum] + + else: # if the selected tab is the one being closed + if tabNum == 0: # if it's the first tab + if len(self.tabs) == 1: # if it's the only tab + self.tabs[tabNum].close() + del self.tabs[tabNum] + self.add(self, "New Note") + + else: # if it's not the only tab + self.tabs[tabNum].close() + del self.tabs[tabNum] + self.selectedTab = 0 + self.switch(1, self.selectedTab) + + elif tabNum == len(self.tabs) - 1: # if it's the last tab + self.tabs[tabNum].close() + del self.tabs[tabNum] + + if self.selectedTab == tabNum: + self.selectedTab -= 1 + + self.switch(1, self.selectedTab) + + else: # if it's somewhere in the middle + if self.selectedTab == tabNum: + self.selectedTab -= 1 + + self.tabs[tabNum].close() + del self.tabs[tabNum] + self.switch(1, self.selectedTab) + + print("Closing tab" + str(tabNum)) + + def renameCurrent(self, name): + self.tabs[self.selectedTab].rename(name) + + def resetChanged(self): + self.tabs[self.selectedTab].change = False + + def checkFilename(self): + return self.tabs[self.selectedTab].fileName + + class tab(): + def __init__(self, master, title): + self.master = master + + self.text = StringVar() + self.text.set("") + self.text.trace("w", lambda *args: self.changeMade()) + + self.Frame = Frame(self.master.Frame, style = "Tab.TFrame") + + self.changed = False + self.fileName = "" + + self.title = Label(self.Frame, style = "TT.TLabel") + self.rename(title) + + self.titleBox = Entry(self.Frame, width = 20) + self.titleBox.insert(0, title) + + self.closeButton = Button(self.Frame, style = "Tab.TButton", text = "X", width = 1) + + self.bindings() + + self.show() + + def bindings(self): + self.title.bind("", lambda event: self.master.switch(1, self.findPlace())) + self.Frame.bind("", lambda event: self.master.switch(1, self.findPlace())) + self.closeButton.bind("", lambda event: self.master.closeSpecific(self.findPlace())) + + def findPlace(self): + return self.master.tabs.index(self) + + def show(self): + self.Frame.pack(side = LEFT, expand = 0, ipadx = 2, ipady = 2, padx = 4, pady = 2) + self.title.pack(side = LEFT, expand = 0, ipadx = 4, ipady = 2, padx = 4) + self.closeButton.pack(side = LEFT, expand = 0, ipadx = 1, ipady = 0) + + self.select() + + def select(self): + self.Frame.config(style = "TabSelected.TFrame") + self.title.config(style = "TTS.TLabel") + + def deselect(self): + self.Frame.config(style = "Tab.TFrame") + self.title.config(style = "TT.TLabel") + + def rename(self, name): + self.longTitle = " " + name + if len(name) > 16: + name = name[:16] + "..." + self.title.config(text = name) + + def changeMade(self): + self.changed = True + + def changeSaved(self): + self.changed = False + + def close(self): + self.Frame.pack_forget() + + # save file \ No newline at end of file