From 1c58306d7a424ef0850e1b6c56afd8508658b98a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 29 Nov 2011 13:35:13 +0900 Subject: [PATCH] new toolbar for the browser --- aqt/browser.py | 120 +++++++++++++++++----- aqt/editor.py | 1 - aqt/main.py | 16 ++- aqt/stats.py | 6 +- aqt/tagedit.py | 6 +- aqt/toolbar.py | 13 ++- aqt/utils.py | 2 +- aqt/webview.py | 5 +- designer/browser.ui | 181 ++++++++++++++------------------- designer/icons.qrc | 4 + designer/icons/pause16.png | Bin 0 -> 512 bytes designer/icons/pause_off16.png | Bin 0 -> 492 bytes designer/icons/star16.png | Bin 0 -> 3128 bytes designer/icons/star_off16.png | Bin 0 -> 3131 bytes designer/main.ui | 6 ++ 15 files changed, 211 insertions(+), 149 deletions(-) create mode 100644 designer/icons/pause16.png create mode 100644 designer/icons/pause_off16.png create mode 100644 designer/icons/star16.png create mode 100644 designer/icons/star_off16.png diff --git a/aqt/browser.py b/aqt/browser.py index 7a9f91e89..421f4e063 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -15,6 +15,7 @@ from anki.errors import * from anki.db import * from anki.hooks import runHook, addHook, removeHook from aqt.webview import AnkiWebView +from aqt.toolbar import Toolbar COLOUR_SUSPENDED1 = "#ffffcc" COLOUR_SUSPENDED2 = "#ffffaa" @@ -304,15 +305,14 @@ class Browser(QMainWindow): self.lastFilter = "" self.form = aqt.forms.browser.Ui_Dialog() self.form.setupUi(self) - self.setUnifiedTitleAndToolBarOnMac(True) - restoreGeom(self, "editor", 38) + #self.setUnifiedTitleAndToolBarOnMac(True) + restoreGeom(self, "editor", 0) restoreState(self, "editor") restoreSplitter(self.form.splitter_2, "editor2") restoreSplitter(self.form.splitter, "editor3") self.form.splitter_2.setChildrenCollapsible(False) self.form.splitter.setChildrenCollapsible(False) self.setupColumns() - self.setupToolbar() self.setupTable() self.setupMenus() self.setupSearch() @@ -320,7 +320,8 @@ class Browser(QMainWindow): self.setupHeaders() self.setupHooks() self.setupEditor() - self.setupCardInfo() + self.setupToolbar() + #self.setupCardInfo() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) self.form.searchEdit.setFocus() @@ -330,8 +331,10 @@ class Browser(QMainWindow): self.onSearch() def setupToolbar(self): - self.form.toolBar.setIconSize(QSize(24, 24)) - self.form.toolBar.toggleViewAction().setText(_("Toggle Toolbar")) + self.toolbarWeb = AnkiWebView() + self.toolbarWeb.setFixedHeight(34) + self.toolbar = BrowserToolbar(self.mw, self.toolbarWeb, self) + self.form.verticalLayout_3.insertWidget(0, self.toolbarWeb) def setupMenus(self): # actions @@ -491,7 +494,7 @@ class Browser(QMainWindow): def onRowChanged(self, current, previous): "Update current note and hide/show editor." show = self.model.cards and self.updateTitle() == 1 - self.form.splitter_2.widget(1).setShown(not not show) + self.form.splitter.widget(1).setShown(not not show) if not show: self.editor.setNote(None) else: @@ -500,7 +503,7 @@ class Browser(QMainWindow): self.editor.setNote(self.card.note()) self.editor.card = self.card self.showCardInfo(self.card) - self.updateToggles() + self.toolbar.draw() def refreshCurrentCard(self, note): self.model.refreshNote(note) @@ -690,6 +693,7 @@ class Browser(QMainWindow): self.onCardLink) def showCardInfo(self, card): + return self.cardStats.card = self.card rep = self.cardStats.report() rep = "" + rep @@ -858,10 +862,10 @@ where id in %s""" % ids2str(sf)) self.model.endReset() self.mw.requireReset() - # Group change + # Deck change ###################################################################### - def setGroup(self, initial=False): + def setDeck(self, initial=False): d = QDialog(self) d.setWindowModality(Qt.WindowModal) frm = aqt.forms.setgroup.Ui_Dialog() @@ -870,7 +874,7 @@ where id in %s""" % ids2str(sf)) te = TagEdit(d, type=1) frm.groupBox.layout().insertWidget(0, te) te.setCol(self.col) - d.connect(d, SIGNAL("accepted()"), lambda: self.onSetGroup(frm, te)) + d.connect(d, SIGNAL("accepted()"), lambda: self._onSetDeck(frm, te)) self.setTabOrder(frm.setCur, te) self.setTabOrder(te, frm.setInitial) if initial: @@ -878,22 +882,22 @@ where id in %s""" % ids2str(sf)) d.show() te.setFocus() - def onSetGroup(self, frm, te): + def _onSetDeck(self, frm, te): self.model.beginReset() - self.mw.checkpoint(_("Set Group")) + self.mw.checkpoint(_("Set Deck")) mod = intTime() if frm.setCur.isChecked(): - gid = self.col.groups.id(unicode(te.text())) + did = self.col.decks.id(unicode(te.text())) self.col.db.execute( - "update cards set mod=?, gid=? where id in " + ids2str( - self.selectedCards()), mod, gid) + "update cards set mod=?, did=? where id in " + ids2str( + self.selectedCards()), mod, did) if frm.setInitial.isChecked(): self.col.db.execute( - "update notes set mod=?, gid=? where id in " + ids2str( - self.selectedNotes()), mod, gid) + "update notes set mod=?, did=? where id in " + ids2str( + self.selectedNotes()), mod, did) else: self.col.db.execute(""" -update cards set mod=?, gid=(select gid from notes where id = cards.nid) +update cards set mod=?, did=(select did from notes where id = cards.nid) where id in %s""" % ids2str(self.selectedCards()), mod) self.onSearch(reset=False) self.mw.requireReset() @@ -932,14 +936,12 @@ where id in %s""" % ids2str(self.selectedCards()), mod) # Suspending and marking ###################################################################### - def updateToggles(self): - self.form.actionToggleSuspend.setChecked(self.isSuspended()) - self.form.actionToggleMark.setChecked(self.isMarked()) - def isSuspended(self): return not not (self.card and self.card.queue == -1) - def onSuspend(self, sus): + def onSuspend(self, sus=None): + if sus is None: + sus = not self.isSuspended() # focus lost hook may not have chance to fire self.editor.saveNow() c = self.selectedCards() @@ -953,7 +955,9 @@ where id in %s""" % ids2str(self.selectedCards()), mod) def isMarked(self): return not not (self.card and self.card.note().hasTag("Marked")) - def onMark(self, mark): + def onMark(self, mark=None): + if mark is None: + mark = not self.isMarked() if mark: self.addTags(tags="marked", label=False) else: @@ -1397,3 +1401,69 @@ Are you sure you want to continue?""")): def onHelp(self): openHelp("Browser#ChangeModel") + +# Toolbar +###################################################################### + +class BrowserToolbar(Toolbar): + always = [ + ["setDeck", "Move to Deck"], + ["addTags", "Add Tags"], + ["remTags", "Remove Tags"], + ] + + singleOnly = [ + ["info", "Info"], + ] + + rightIcons = [ + ["mark", "qrc:/icons/star16.png"], + ["pause", "qrc:/icons/star_off16.png"], + ] + + def __init__(self, mw, web, browser): + self.browser = browser + Toolbar.__init__(self, mw, web) + + def draw(self): + single = self.browser.editor.note + mark = self.browser.isMarked() + pause = self.browser.isSuspended() + links = self.always[:] + if single: + links += self.singleOnly + self.centerLinks = links + def borderImg(link, icon, on): + if on: + fmt = '''\ + +''' + else: + fmt = '''\ +''' + return fmt % (link, icon) + right = "" + right += borderImg("mark", "star16", mark) + right += borderImg("pause", "pause16", pause) + self.web.stdHtml(self._body % ( + 'Browser ▾', + self._centerLinks(), + right), self._css, focus=False) + + # Link handling + ###################################################################### + + def _linkHandler(self, l): + if l == "anki": + self.showMenu() + elif l == "setDeck": + self.browser.setDeck() + elif l == "addTags": + self.browser.addTags() + elif l == "remTags": + self.browser.deleteTags() + # icons + elif l == "mark": + self.browser.onMark() + elif l == "pause": + self.browser.onSuspend() diff --git a/aqt/editor.py b/aqt/editor.py index 040d0fa45..25318d515 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -426,7 +426,6 @@ class Editor(object): def showDupes(self): contents = self.note.fields[0] - print "conts", `self.note.fields` browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.setText( "'model:%s' '%s'" % (self.note.model()['name'], contents)) diff --git a/aqt/main.py b/aqt/main.py index dddf41178..e46303607 100755 --- a/aqt/main.py +++ b/aqt/main.py @@ -322,10 +322,12 @@ title="%s">%s''' % ( tweb.setFocusPolicy(Qt.WheelFocus) tweb.setFixedHeight(32) self.toolbar = aqt.toolbar.Toolbar(self, tweb) + self.toolbar.draw() # main area self.web = aqt.webview.AnkiWebView() self.web.setObjectName("mainText") self.web.setFocusPolicy(Qt.WheelFocus) + self.web.setMinimumWidth(400) # add in a layout self.mainLayout = QVBoxLayout() self.mainLayout.setContentsMargins(0,0,0,0) @@ -527,17 +529,21 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") # Dockable widgets ########################################################################## - def addDockable(self, title, w): - dock = QDockWidget(title, self) + def addDockable(self, title, w, target=None): + target = target or self + dock = QDockWidget(title, target) dock.setObjectName(title) dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) dock.setFeatures(QDockWidget.DockWidgetClosable) dock.setWidget(w) - self.addDockWidget(Qt.RightDockWidgetArea, dock) + if target.width() < 600: + target.resize(QSize(600, target.height())) + target.addDockWidget(Qt.RightDockWidgetArea, dock) return dock - def rmDockable(self, dock): - self.removeDockWidget(dock) + def rmDockable(self, dock, target=None): + target = target or self + target.removeDockWidget(dock) # Marking, suspending and deleting ########################################################################## diff --git a/aqt/stats.py b/aqt/stats.py index 3cc77c49f..ad14a21b5 100644 --- a/aqt/stats.py +++ b/aqt/stats.py @@ -23,8 +23,10 @@ class CardStats(object): def show(self): if not self.shown: - self.web = AnkiWebView() - self.web.setMaximumWidth(400) + class ThinAnkiWebView(AnkiWebView): + def sizeHint(self): + return QSize(200, 100) + self.web = ThinAnkiWebView() self.shown = self.mw.addDockable(_("Card Info"), self.web) self.shown.connect(self.shown, SIGNAL("visibilityChanged(bool)"), self._visChange) diff --git a/aqt/tagedit.py b/aqt/tagedit.py index b2da32913..66edb2b6b 100644 --- a/aqt/tagedit.py +++ b/aqt/tagedit.py @@ -6,7 +6,7 @@ import re, sys class TagEdit(QLineEdit): - # 0 = tags, 1 = groups + # 0 = tags, 1 = decks def __init__(self, parent, type=0): QLineEdit.__init__(self, parent) self.col = None @@ -26,7 +26,7 @@ class TagEdit(QLineEdit): if self.type == 0: l = sorted(self.col.tags.all()) else: - l = self.col.groups.all() + l = sorted(self.col.decks.allNames()) self.model.setStringList(l) def addTags(self, tags): @@ -50,7 +50,7 @@ class TagCompleter(QCompleter): def splitPath(self, str): str = unicode(str).strip() str = re.sub(" +", " ", str) - self.tags = self.parent.col.tags.split(str) + self.tags = self.edit.col.tags.split(str) self.tags.append(u"") p = self.edit.cursorPosition() self.cursor = str.count(" ", 0, p) diff --git a/aqt/toolbar.py b/aqt/toolbar.py index d748f9b5a..b40310d3c 100644 --- a/aqt/toolbar.py +++ b/aqt/toolbar.py @@ -13,11 +13,13 @@ class Toolbar(object): self.web.page().mainFrame().setScrollBarPolicy( Qt.Vertical, Qt.ScrollBarAlwaysOff) self.web.setLinkHandler(self._linkHandler) - self.draw() def draw(self): - body = self._body % (self._centerLinks(), self._rightIcons()) - self.web.stdHtml(body, self._css) + self.web.stdHtml(self._body % ( + 'Anki ▾', + self._centerLinks(), + self._rightIcons()), + self._css, focus=False) # Available links ###################################################################### @@ -38,6 +40,7 @@ class Toolbar(object): buf = "" for ln, name in self.centerLinks: buf += '%s' % (ln, _(name)) + buf += " "*3 return buf def _rightIcons(self): @@ -72,9 +75,9 @@ class Toolbar(object): _body = """ - + - + """ diff --git a/aqt/utils.py b/aqt/utils.py index 5a4f53904..a3a25e2ef 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -208,7 +208,7 @@ def chooseList(prompt, choices, startrow=0, parent=None): def getTag(parent, deck, question, tags="user", **kwargs): from aqt.tagedit import TagEdit te = TagEdit(parent) - te.setDeck(deck) + te.setCol(deck) return getText(question, parent, edit=te, **kwargs) # File handling diff --git a/aqt/webview.py b/aqt/webview.py index 0e13f3cc0..16ee77a2c 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -83,7 +83,7 @@ class AnkiWebView(QWebView): if loadCB: self._loadFinishedCB = loadCB QWebView.setHtml(self, html) - def stdHtml(self, body, css="", bodyID="", loadCB=None): + def stdHtml(self, body, css="", bodyID="", loadCB=None, focus=True): self.setHtml(""" @@ -91,7 +91,8 @@ class AnkiWebView(QWebView): %s""" % ( css, anki.js.all, bodyID, body), loadCB) # ensure we're focused - self.setFocus() + if focus: + self.setFocus() def setBridge(self, bridge): self._bridge.setBridge(bridge) def eval(self, js): diff --git a/designer/browser.ui b/designer/browser.ui index b4eff13ce..2c129f7e2 100644 --- a/designer/browser.ui +++ b/designer/browser.ui @@ -6,10 +6,16 @@ 0 0 - 524 - 382 + 610 + 430 + + + 400 + 0 + + Browser @@ -19,34 +25,40 @@ + + 0 + 0 - Qt::Vertical + Qt::Horizontal + + + + 0 + 0 + + + + QFrame::NoFrame + + + false + + + + 1 + + + - Qt::Horizontal + Qt::Vertical - - - - 1 - 0 - - - - false - - - - 1 - - - @@ -98,7 +110,7 @@ - 4 + 0 4 @@ -142,58 +154,48 @@ - - - - - 0 - - - 0 - - - 1 - - - 0 - - - 0 - - - - - 0 - - - - - - 7 - 0 - - - - - 50 - 50 - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 6 - - - - - - + + + + 0 + + + 0 + + + 1 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + + 50 + 50 + + + + + + + + @@ -204,7 +206,7 @@ 0 0 - 524 + 610 22 @@ -274,37 +276,6 @@ - - - toolBar - - - - 24 - 24 - - - - Qt::ToolButtonIconOnly - - - TopToolBarArea - - - false - - - - - - - - - - - - - diff --git a/designer/icons.qrc b/designer/icons.qrc index cfde582db..81b4057e7 100644 --- a/designer/icons.qrc +++ b/designer/icons.qrc @@ -117,5 +117,9 @@ icons/stock_new_template_green.png icons/stock_new_template_red.png icons/stock_group.png + icons/star16.png + icons/star_off16.png + icons/pause16.png + icons/pause_off16.png diff --git a/designer/icons/pause16.png b/designer/icons/pause16.png new file mode 100644 index 0000000000000000000000000000000000000000..1ece3cbbdc92e4bd6bb4a51b8907ff2f2e91a632 GIT binary patch literal 512 zcmV+b0{{JqP)Px#0%A)?L;(MXkIcUS000SaNLh0L00VXa00VXbebs`@00007bV*G`2iprB10WA( z^M7>!00D$aL_t(2&rQ)kOO$Z{!12%TxhJzOEkQjcotheQ=pD3NZy+^n3A~md3@WIO ziY_domUeFmLNH&!y@A$Jf)E;#GyIpQQQ&jlJ->z;+P}+(Z{V@$nHQx_H8008LNjTk z^AENj32MT99j`rE=*(_A<1`NJ8-vg&sgC-kYgC6T!5 zl77eBv>~e|S$W7{D4^>AbnN1|;V>#IC+)ETQN&m4pk)K8U{rf7%|(fTk3NHKEd+_6 zE6JNXy1F=J2ow#Wi>BC2G}i6K45^bKRYb=@T%!?YN=KW;_%ihB|Brbnx*hjI&q!hQ z_r&<_#b0qE93h}1!`t{F6zhwNrZK&8IbP4d CrQhTL literal 0 HcmV?d00001 diff --git a/designer/icons/pause_off16.png b/designer/icons/pause_off16.png new file mode 100644 index 0000000000000000000000000000000000000000..81ae20367b11a7110d57597804741f95cdbc99a4 GIT binary patch literal 492 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 + + + 400 + 0 + + Anki