mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
profile gui, new deck browser
This commit is contained in:
parent
f4150a5df4
commit
7c68b58d44
10 changed files with 379 additions and 396 deletions
|
@ -5,7 +5,7 @@ import os, sys
|
|||
from aqt.qt import *
|
||||
|
||||
appName="Anki"
|
||||
appVersion="1.99"
|
||||
appVersion="2.0-alpha2"
|
||||
appWebsite="http://ankisrs.net/"
|
||||
appHelpSite="http://ankisrs.net/docs/dev/"
|
||||
appDonate="http://ankisrs.net/support/"
|
||||
|
@ -17,14 +17,6 @@ moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
|
|||
# if hasattr(sys, "frozen"):
|
||||
# sys.path.append(moduleDir)
|
||||
|
||||
def openHelp(name):
|
||||
if "#" in name:
|
||||
name = name.split("#")
|
||||
name = name[0] + ".html#" + name[1]
|
||||
else:
|
||||
name = name + ".html"
|
||||
QDesktopServices.openUrl(QUrl(appHelpSite + name))
|
||||
|
||||
# Dialog manager - manages modeless windows
|
||||
##########################################################################
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ from anki.hooks import runHook
|
|||
class AddonManager(object):
|
||||
|
||||
def __init__(self, mw):
|
||||
print "addons"
|
||||
return
|
||||
self.mw = mw
|
||||
f = self.mw.form; s = SIGNAL("triggered()")
|
||||
self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenPluginFolder)
|
||||
|
|
|
@ -2,75 +2,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import time, os, stat, shutil, re
|
||||
from operator import itemgetter
|
||||
from aqt.qt import *
|
||||
from anki import Deck
|
||||
from anki.utils import fmtTimeSpan
|
||||
from anki.hooks import addHook
|
||||
import aqt
|
||||
from aqt.utils import askUser
|
||||
|
||||
class DeckBrowser(object):
|
||||
"Display a list of remembered decks."
|
||||
|
||||
def __init__(self, mw):
|
||||
self.mw = mw
|
||||
self.web = mw.web
|
||||
self._browserLastRefreshed = 0
|
||||
self._decks = []
|
||||
addHook("deckClosing", self._onClose)
|
||||
|
||||
def show(self, _init=True):
|
||||
if _init:
|
||||
self.web.setLinkHandler(self._linkHandler)
|
||||
self.web.setKeyHandler(self._keyHandler)
|
||||
self._setupToolbar()
|
||||
# refresh or reorder
|
||||
self._checkDecks()
|
||||
# show
|
||||
self._renderPage()
|
||||
|
||||
def _onClose(self):
|
||||
# update counts
|
||||
deck = self.mw.deck
|
||||
def add(d):
|
||||
counts = deck.sched.counts()
|
||||
d['due'] = counts[1]+counts[2]
|
||||
d['new'] = counts[0]
|
||||
d['mod'] = deck.mod
|
||||
d['time'] = deck.sched.timeToday()
|
||||
d['reps'] = deck.sched.repsToday()
|
||||
d['name'] = deck.name()
|
||||
for d in self._decks:
|
||||
if d['path'] == deck.path:
|
||||
add(d)
|
||||
return
|
||||
# not found; add new
|
||||
d = {'path': deck.path, 'state': 'ok'}
|
||||
add(d)
|
||||
self._decks.append(d)
|
||||
|
||||
# Toolbar
|
||||
##########################################################################
|
||||
|
||||
# we don't use the top toolbar
|
||||
def _setupToolbar(self):
|
||||
self.mw.form.toolBar.hide()
|
||||
|
||||
# instead we have a html one under the deck list
|
||||
def _toolbar(self):
|
||||
items = [
|
||||
("download", _("Download")),
|
||||
("new", _("Create")),
|
||||
# ("import", _("Import")),
|
||||
("opensel", _("Open")),
|
||||
# ("synced", _("Synced")),
|
||||
("refresh", _("Refresh")),
|
||||
]
|
||||
h = "".join([self.mw.button(
|
||||
link=row[0], name=row[1]) for row in items])
|
||||
return h
|
||||
|
||||
# Event handlers
|
||||
##########################################################################
|
||||
|
||||
|
@ -81,7 +26,7 @@ class DeckBrowser(object):
|
|||
cmd = url
|
||||
if cmd == "open":
|
||||
deck = self._decks[int(arg)]
|
||||
self._loadDeck(deck)
|
||||
self._selDeck(deck)
|
||||
elif cmd == "opts":
|
||||
self._optsForRow(int(arg))
|
||||
elif cmd == "download":
|
||||
|
@ -97,177 +42,87 @@ class DeckBrowser(object):
|
|||
elif cmd == "refresh":
|
||||
self.refresh()
|
||||
|
||||
def _keyHandler(self, evt):
|
||||
txt = evt.text()
|
||||
if ((txt >= "0" and txt <= "9") or
|
||||
(txt >= "a" and txt <= "z")):
|
||||
self._openAccel(txt)
|
||||
return True
|
||||
|
||||
def _openAccel(self, txt):
|
||||
for d in self._decks:
|
||||
if d['accel'] == txt:
|
||||
self._loadDeck(d)
|
||||
|
||||
def _loadDeck(self, rec):
|
||||
if rec['state'] == 'ok':
|
||||
self.mw.loadDeck(rec['path'])
|
||||
def _selDeck(self, rec):
|
||||
print rec
|
||||
|
||||
# HTML generation
|
||||
##########################################################################
|
||||
|
||||
_css = """
|
||||
.sub { color: #555; }
|
||||
a.deck { color: #000; text-decoration: none; font-size: 100%; }
|
||||
a.deck { color: #000; text-decoration: none; font-size: 16px; }
|
||||
.num { text-align: right; padding: 0 5 0 5; }
|
||||
td.opts { text-align: right; white-space: nowrap; }
|
||||
td.menu { text-align: center; }
|
||||
td.opts { white-space: nowrap; }
|
||||
td.deck { width: 90% }
|
||||
a { font-size: 80%; }
|
||||
.extra { font-size: 90%; }
|
||||
.due { vertical-align: text-bottom; }
|
||||
table { margin: 1em; }
|
||||
"""
|
||||
|
||||
_body = """
|
||||
<center>
|
||||
<h1>%(title)s</h1>
|
||||
%(tb)s
|
||||
<p>
|
||||
<table cellspacing=0 cellpadding=3 width=100%%>
|
||||
%(rows)s
|
||||
<table cellspacing=0 cellpading=3 width=100%%>
|
||||
%(tree)s
|
||||
</table>
|
||||
<div class="extra">
|
||||
%(extra)s
|
||||
</div>
|
||||
</center>
|
||||
"""
|
||||
|
||||
def _renderPage(self):
|
||||
css = self.mw.sharedCSS + self._css
|
||||
if self._decks:
|
||||
buf = ""
|
||||
max=len(self._decks)-1
|
||||
buf += "<tr><th></th><th align=right>%s</th>" % _("Due")
|
||||
buf += "<th align=right>%s</th><th></th></tr>" % _("New")
|
||||
for c, deck in enumerate(self._decks):
|
||||
buf += self._deckRow(c, max, deck)
|
||||
tree = self._renderDeckTree(self.mw.col.sched.deckDueTree())
|
||||
self.web.stdHtml(self._body%dict(
|
||||
title=_("Decks"),
|
||||
rows=buf,
|
||||
tb=self._toolbar(),
|
||||
extra="<p>%s<p>%s" % (
|
||||
self._summary(),
|
||||
_("Click a deck to open it, or type a number."))),
|
||||
css)
|
||||
else:
|
||||
self.web.stdHtml(self._body%dict(
|
||||
title=_("Welcome!"),
|
||||
rows="<tr><td align=center>%s</td></tr>"%_(
|
||||
"Click <b>Download</b> to get started."),
|
||||
extra="",
|
||||
tb=self._toolbar()),
|
||||
css)
|
||||
tree=tree), css=css)
|
||||
|
||||
def _deckRow(self, c, max, deck):
|
||||
buf = "<tr>"
|
||||
ok = deck['state'] == 'ok'
|
||||
def accelName(deck):
|
||||
if deck['accel']:
|
||||
return "%s. " % deck['accel']
|
||||
def _renderDeckTree(self, nodes, depth=0):
|
||||
if not nodes:
|
||||
return ""
|
||||
if ok:
|
||||
# name/link
|
||||
buf += "<td>%s<b>%s</b></td>" % (
|
||||
accelName(deck),
|
||||
"<a class=deck href='open:%d'>%s</a>"%(c, deck['name']))
|
||||
# due
|
||||
col = '<td class=num><b><font color=#0000ff>%s</font></b></td>'
|
||||
if deck['due'] > 0:
|
||||
s = col % str(deck['due'])
|
||||
else:
|
||||
s = col % ""
|
||||
buf += s
|
||||
# new
|
||||
if deck['new']:
|
||||
s = str(deck['new'])
|
||||
else:
|
||||
s = ""
|
||||
buf += "<td class=num>%s</td>" % s
|
||||
else:
|
||||
# name/error
|
||||
if deck['state'] == 'missing':
|
||||
sub = _("(moved or removed)")
|
||||
elif deck['state'] == 'corrupt':
|
||||
sub = _("(corrupt)")
|
||||
elif deck['state'] == 'in use':
|
||||
sub = _("(already open)")
|
||||
else:
|
||||
sub = "unknown"
|
||||
buf += "<td>%s<b>%s</b><br><span class=sub>%s</span></td>" % (
|
||||
accelName(deck),
|
||||
deck['name'],
|
||||
sub)
|
||||
# no counts
|
||||
buf += "<td colspan=2></td>"
|
||||
# options
|
||||
buf += "<td class=opts>%s</td>" % (
|
||||
self.mw.button(link="opts:%d"%c, name=_("Options")+'▼'))
|
||||
buf += "</tr>"
|
||||
buf = ""
|
||||
for node in nodes:
|
||||
buf += self._deckRow(node, depth)
|
||||
return buf
|
||||
|
||||
def _summary(self):
|
||||
# summarize
|
||||
reps = 0
|
||||
mins = 0
|
||||
revC = 0
|
||||
newC = 0
|
||||
for d in self._decks:
|
||||
if d['state']=='ok':
|
||||
reps += d['reps']
|
||||
mins += d['time']
|
||||
revC += d['due']
|
||||
newC += d['new']
|
||||
line1 = ngettext(
|
||||
"Studied <b>%(reps)d card</b> in <b>%(time)s</b> today.",
|
||||
"Studied <b>%(reps)d cards</b> in <b>%(time)s</b> today.",
|
||||
reps) % {
|
||||
'reps': reps,
|
||||
'time': fmtTimeSpan(mins, point=2),
|
||||
}
|
||||
rev = ngettext(
|
||||
"<b><font color=#0000ff>%d</font></b> review",
|
||||
"<b><font color=#0000ff>%d</font></b> reviews",
|
||||
revC) % revC
|
||||
new = ngettext("<b>%d</b> new card", "<b>%d</b> new cards", newC) % newC
|
||||
line2 = _("Due: %(rev)s, %(new)s") % {
|
||||
'rev': rev, 'new': new}
|
||||
return line1+'<br>'+line2
|
||||
def _deckRow(self, node, depth):
|
||||
name, did, due, new, children = node
|
||||
# due image
|
||||
buf = "<tr><td colspan=5>" + self._dueImg(due, new)
|
||||
# deck link
|
||||
buf += " <a class=deck href='open:%d'>%s</a></td>"% (did, name)
|
||||
# options
|
||||
buf += "<td align=right class=opts>%s</td></tr>" % self.mw.button(
|
||||
link="opts:%d"%did, name=_("Options")+'▼')
|
||||
# children
|
||||
buf += self._renderDeckTree(children, depth+1)
|
||||
return buf
|
||||
|
||||
def _dueImg(self, due, new):
|
||||
if due and new:
|
||||
i = "both"
|
||||
elif due:
|
||||
i = "green"
|
||||
elif new:
|
||||
i = "blue"
|
||||
else:
|
||||
i = "none"
|
||||
return '<img valign=bottom src="qrc:/icons/%s.png">' % i
|
||||
|
||||
# Options
|
||||
##########################################################################
|
||||
|
||||
def _optsForRow(self, n):
|
||||
m = QMenu(self.mw)
|
||||
# hide
|
||||
a = m.addAction(QIcon(":/icons/edit-undo.png"), _("Hide From List"))
|
||||
a.connect(a, SIGNAL("triggered()"), lambda n=n: self._hideRow(n))
|
||||
# delete
|
||||
a = m.addAction(QIcon(":/icons/editdelete.png"), _("Delete"))
|
||||
a.connect(a, SIGNAL("triggered()"), lambda n=n: self._deleteRow(n))
|
||||
m.exec_(QCursor.pos())
|
||||
|
||||
def _hideRow(self, c):
|
||||
d = self._decks[c]
|
||||
if d['state'] == "missing" or aqt.utils.askUser(_("""\
|
||||
Hide %s from the list? You can File>Open it again later.""") %
|
||||
d['name']):
|
||||
self.mw.config.delRecentDeck(d['path'])
|
||||
del self._decks[c]
|
||||
self.refresh()
|
||||
|
||||
def _deleteRow(self, c):
|
||||
d = self._decks[c]
|
||||
if d['state'] == 'missing':
|
||||
return self._hideRow(c)
|
||||
if aqt.utils.askUser(_("""\
|
||||
if askUser(_("""\
|
||||
Delete %s? If this deck is synchronized the online version will \
|
||||
not be touched.""") % d['name']):
|
||||
deck = d['path']
|
||||
|
@ -278,71 +133,3 @@ not be touched.""") % d['name']):
|
|||
pass
|
||||
self.mw.config.delRecentDeck(deck)
|
||||
self.refresh()
|
||||
|
||||
# Data gathering
|
||||
##########################################################################
|
||||
|
||||
def _checkDecks(self):
|
||||
self._decks = []
|
||||
decks = self.mw.config.recentDecks()
|
||||
if not decks:
|
||||
return
|
||||
tx = time.time()
|
||||
self.mw.progress.start(max=len(decks))
|
||||
for c, d in enumerate(decks):
|
||||
self.mw.progress.update(_("Checking deck %(x)d of %(y)d...") % {
|
||||
'x': c+1, 'y': len(decks)})
|
||||
base = os.path.basename(d)
|
||||
if not os.path.exists(d):
|
||||
self._decks.append({'name': base, 'state': 'missing', 'path':d})
|
||||
continue
|
||||
try:
|
||||
mod = os.stat(d)[stat.ST_MTIME]
|
||||
t = time.time()
|
||||
deck = Deck(d, queue=False, lock=False)
|
||||
counts = deck.sched.selCounts()
|
||||
dtime = deck.sched.timeToday()
|
||||
dreps = deck.sched.repsToday()
|
||||
self._decks.append({
|
||||
'path': d,
|
||||
'state': 'ok',
|
||||
'name': deck.name(),
|
||||
'due': counts[1]+counts[2],
|
||||
'new': counts[0],
|
||||
'mod': deck.mod,
|
||||
# these multiply deck check time by a factor of 6
|
||||
'time': dtime,
|
||||
'reps': dreps
|
||||
})
|
||||
deck.close(save=False)
|
||||
# reset modification time for the sake of backup systems
|
||||
try:
|
||||
os.utime(d, (mod, mod))
|
||||
except:
|
||||
# some misbehaving filesystems may fail here
|
||||
pass
|
||||
except Exception, e:
|
||||
if "locked" in unicode(e):
|
||||
state = "in use"
|
||||
else:
|
||||
state = "corrupt"
|
||||
self._decks.append({'name': base, 'state':state, 'path':d})
|
||||
self.mw.progress.finish()
|
||||
self._browserLastRefreshed = time.time()
|
||||
self._reorderDecks()
|
||||
|
||||
def _reorderDecks(self):
|
||||
# for now, sort by deck name
|
||||
self._decks.sort(key=itemgetter('name'))
|
||||
# after the decks are sorted, assign shortcut keys to them
|
||||
for c, d in enumerate(self._decks):
|
||||
if c > 35:
|
||||
d['accel'] = None
|
||||
elif c < 9:
|
||||
d['accel'] = str(c+1)
|
||||
else:
|
||||
d['accel'] = ord('a')+(c-10)
|
||||
|
||||
def refresh(self):
|
||||
self._browserLastRefreshed = 0
|
||||
self.show(_init=False)
|
||||
|
|
269
aqt/main.py
269
aqt/main.py
|
@ -20,38 +20,29 @@ from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
|
|||
saveState, restoreState, getOnlyText, askUser, GetTextDialog, \
|
||||
askUserDialog, applyStyles, getText, showText, showCritical, getFile
|
||||
|
||||
config = aqt.config
|
||||
|
||||
## fixme: open plugin folder broken on win32?
|
||||
|
||||
## models remembering the previous group
|
||||
|
||||
class AnkiQt(QMainWindow):
|
||||
def __init__(self, app, config, args):
|
||||
def __init__(self, app, profileManager):
|
||||
QMainWindow.__init__(self)
|
||||
aqt.mw = self
|
||||
self.app = app
|
||||
self.config = config
|
||||
self.pm = profileManager
|
||||
try:
|
||||
# initialize everything
|
||||
self.setup()
|
||||
# load plugins
|
||||
self.setupUI()
|
||||
self.setupAddons()
|
||||
# show main window
|
||||
self.show()
|
||||
# raise window for osx
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
#
|
||||
self.setupProfile()
|
||||
except:
|
||||
showInfo("Error during startup:\n%s" % traceback.format_exc())
|
||||
sys.exit(1)
|
||||
|
||||
def setup(self):
|
||||
def setupUI(self):
|
||||
self.col = None
|
||||
self.state = None
|
||||
self.setupLang("en") # bootstrap with english; profile will adjust
|
||||
self.setupThreads()
|
||||
self.setupLang()
|
||||
self.setupMainWindow()
|
||||
self.setupStyle()
|
||||
self.setupProxy()
|
||||
|
@ -61,17 +52,136 @@ class AnkiQt(QMainWindow):
|
|||
self.setupErrorHandler()
|
||||
self.setupSystemSpecific()
|
||||
self.setupSignals()
|
||||
self.setupVersion()
|
||||
self.setupAutoUpdate()
|
||||
self.setupUpgrade()
|
||||
self.setupCardStats()
|
||||
self.setupSchema()
|
||||
self.updateTitleBar()
|
||||
# screens
|
||||
#self.setupColBrowser()
|
||||
self.setupDeckBrowser()
|
||||
self.setupOverview()
|
||||
self.setupReviewer()
|
||||
|
||||
# Profiles
|
||||
##########################################################################
|
||||
|
||||
def setupProfile(self):
|
||||
# profile not provided on command line?
|
||||
if False: # not self.pm.name:
|
||||
# if there's a single profile, load it automatically
|
||||
profs = self.pm.profiles()
|
||||
if len(profs) == 1:
|
||||
try:
|
||||
self.pm.load(profs[0])
|
||||
except:
|
||||
# password protected
|
||||
pass
|
||||
if not self.pm.name:
|
||||
self.showProfileManager()
|
||||
else:
|
||||
self.loadProfile()
|
||||
|
||||
def showProfileManager(self):
|
||||
d = self.profileDiag = QDialog()
|
||||
f = self.profileForm = aqt.forms.profiles.Ui_Dialog()
|
||||
f.setupUi(d)
|
||||
d.connect(f.login, SIGNAL("clicked()"), self.onOpenProfile)
|
||||
d.connect(f.quit, SIGNAL("clicked()"), lambda: sys.exit(0))
|
||||
d.connect(f.add, SIGNAL("clicked()"), self.onAddProfile)
|
||||
d.connect(f.delete_2, SIGNAL("clicked()"), self.onRemProfile)
|
||||
d.connect(d, SIGNAL("rejected()"), lambda: d.close())
|
||||
d.connect(f.profiles, SIGNAL("currentRowChanged(int)"),
|
||||
self.onProfileRowChange)
|
||||
self.refreshProfilesList()
|
||||
# raise first, for osx testing
|
||||
d.show()
|
||||
d.activateWindow()
|
||||
d.raise_()
|
||||
d.exec_()
|
||||
|
||||
def refreshProfilesList(self):
|
||||
f = self.profileForm
|
||||
f.profiles.clear()
|
||||
f.profiles.addItems(self.pm.profiles())
|
||||
f.profiles.setCurrentRow(0)
|
||||
|
||||
def onProfileRowChange(self, n):
|
||||
if n < 0:
|
||||
# called on .clear()
|
||||
return
|
||||
name = self.pm.profiles()[n]
|
||||
f = self.profileForm
|
||||
passwd = False
|
||||
try:
|
||||
self.pm.load(name)
|
||||
except:
|
||||
passwd = True
|
||||
f.passEdit.setShown(passwd)
|
||||
f.passLabel.setShown(passwd)
|
||||
|
||||
def openProfile(self):
|
||||
name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
|
||||
passwd = self.profileForm.passEdit.text()
|
||||
try:
|
||||
self.pm.load(name, passwd)
|
||||
except:
|
||||
showWarning(_("Invalid password."))
|
||||
return
|
||||
return True
|
||||
|
||||
def onOpenProfile(self):
|
||||
self.openProfile()
|
||||
self.profileDiag.close()
|
||||
self.loadProfile()
|
||||
return True
|
||||
|
||||
def onAddProfile(self):
|
||||
name = getOnlyText("Name:")
|
||||
if name:
|
||||
if name in self.pm.profiles():
|
||||
return showWarning("Name exists.")
|
||||
if not re.match("^[A-Za-z0-9 ]+$", name):
|
||||
return showWarning(
|
||||
"Only numbers, letters and spaces can be used.")
|
||||
self.pm.create(name)
|
||||
self.refreshProfilesList()
|
||||
|
||||
def onRemProfile(self):
|
||||
profs = self.pm.profiles()
|
||||
if len(profs) < 2:
|
||||
return showWarning("There must be at least one profile.")
|
||||
# password correct?
|
||||
if not self.openProfile():
|
||||
return
|
||||
# sure?
|
||||
if not askUser("""\
|
||||
All cards, notes, and media for this profile will be deleted. \
|
||||
Are you sure?"""):
|
||||
return
|
||||
self.pm.remove(self.pm.name)
|
||||
self.refreshProfilesList()
|
||||
|
||||
def loadProfile(self):
|
||||
self.setupLang()
|
||||
# show main window
|
||||
if self.pm.profile['mainWindowState']:
|
||||
restoreGeom(self, "mainWindow")
|
||||
restoreState(self, "mainWindow")
|
||||
else:
|
||||
self.resize(500, 400)
|
||||
self.show()
|
||||
# raise window for osx
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
# maybe sync
|
||||
self.onSync()
|
||||
# then load collection and launch into the deck browser
|
||||
self.col = Collection(self.pm.collectionPath())
|
||||
self.moveToState("deckBrowser")
|
||||
|
||||
def unloadProfile(self):
|
||||
self.col = None
|
||||
|
||||
# State machine
|
||||
##########################################################################
|
||||
|
||||
|
@ -85,9 +195,8 @@ class AnkiQt(QMainWindow):
|
|||
getattr(self, "_"+state+"State")(oldState, *args)
|
||||
|
||||
def _deckBrowserState(self, oldState):
|
||||
# shouldn't call this directly; call close
|
||||
self.disableColMenuItems()
|
||||
self.closeAllColWindows()
|
||||
self.disableDeckMenuItems()
|
||||
self.closeAllWindows()
|
||||
self.deckBrowser.show()
|
||||
|
||||
def _colLoadingState(self, oldState):
|
||||
|
@ -163,8 +272,7 @@ class AnkiQt(QMainWindow):
|
|||
|
||||
sharedCSS = """
|
||||
body {
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#bbb));
|
||||
/*background: #eee;*/
|
||||
background: #eee;
|
||||
margin: 2em;
|
||||
}
|
||||
a:hover { background-color: #aaa; }
|
||||
|
@ -198,13 +306,8 @@ title="%s">%s</button>''' % (
|
|||
self.mainLayout.setContentsMargins(0,0,0,0)
|
||||
self.form.centralwidget.setLayout(self.mainLayout)
|
||||
addHook("undoEnd", self.maybeEnableUndo)
|
||||
if self.config['mainWindowState']:
|
||||
restoreGeom(self, "mainWindow")
|
||||
restoreState(self, "mainWindow")
|
||||
else:
|
||||
self.resize(500, 400)
|
||||
|
||||
def closeAllColWindows(self):
|
||||
def closeAllWindows(self):
|
||||
aqt.dialogs.closeAll()
|
||||
|
||||
# Components
|
||||
|
@ -223,19 +326,6 @@ title="%s">%s</button>''' % (
|
|||
import aqt.errors
|
||||
self.errorHandler = aqt.errors.ErrorHandler(self)
|
||||
|
||||
def setupVersion(self):
|
||||
# check if we've been updated
|
||||
if "version" not in self.config:
|
||||
# could be new user, or upgrade from older version
|
||||
# which didn't have version variable
|
||||
self.appUpdated = "first"
|
||||
elif self.config['version'] != aqt.appVersion:
|
||||
self.appUpdated = self.config['version']
|
||||
else:
|
||||
self.appUpdated = False
|
||||
if self.appUpdated:
|
||||
self.config['version'] = aqt.appVersion
|
||||
|
||||
def setupAddons(self):
|
||||
import aqt.addons
|
||||
self.addonManager = aqt.addons.AddonManager(self)
|
||||
|
@ -292,7 +382,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
finally:
|
||||
# we may have a progress window open if we were upgrading
|
||||
self.progress.finish()
|
||||
self.config.addRecentDeck(self.col.path)
|
||||
self.pm.profile.addRecentDeck(self.col.path)
|
||||
self.setupMedia(self.col)
|
||||
if not self.upgrading:
|
||||
self.progress.setupDB(self.col.db)
|
||||
|
@ -319,7 +409,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
self.col.reset()
|
||||
runHook("deckClosing")
|
||||
print "focusOut() should be handled with deckClosing now"
|
||||
self.closeAllDeckWindows()
|
||||
self.closeAllWindows()
|
||||
self.col.close()
|
||||
self.col = None
|
||||
if showBrowser:
|
||||
|
@ -329,7 +419,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
##########################################################################
|
||||
|
||||
def onSync(self):
|
||||
return showInfo("not yet implemented")
|
||||
return
|
||||
return showInfo("sync not yet implemented")
|
||||
|
||||
# Tools
|
||||
##########################################################################
|
||||
|
@ -344,7 +435,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
self.form.statusbar.showMessage(text, timeout)
|
||||
|
||||
def setupStyle(self):
|
||||
applyStyles(self)
|
||||
print "applystyles"
|
||||
#applyStyles(self)
|
||||
|
||||
# App exit
|
||||
##########################################################################
|
||||
|
@ -352,11 +444,11 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
def prepareForExit(self):
|
||||
"Save config and window geometry."
|
||||
runHook("quit")
|
||||
self.config['mainWindowGeom'] = self.saveGeometry()
|
||||
self.config['mainWindowState'] = self.saveState()
|
||||
self.pm.profile['mainWindowGeom'] = self.saveGeometry()
|
||||
self.pm.profile['mainWindowState'] = self.saveState()
|
||||
# save config
|
||||
try:
|
||||
self.config.save()
|
||||
self.pm.save()
|
||||
except (IOError, OSError), e:
|
||||
showWarning(_("Anki was unable to save your "
|
||||
"configuration file:\n%s" % e))
|
||||
|
@ -368,7 +460,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
event.ignore()
|
||||
return self.moveToState("saveEdit")
|
||||
self.close(showBrowser=False)
|
||||
# if self.config['syncOnProgramOpen']:
|
||||
# if self.pm.profile['syncOnProgramOpen']:
|
||||
# self.showBrowser = False
|
||||
# self.syncDeck(interactive=False)
|
||||
self.prepareForExit()
|
||||
|
@ -392,18 +484,18 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
|||
tb.addAction(frm.actionStats)
|
||||
tb.addAction(frm.actionMarkCard)
|
||||
tb.addAction(frm.actionRepeatAudio)
|
||||
tb.setIconSize(QSize(self.config['iconSize'],
|
||||
self.config['iconSize']))
|
||||
tb.setIconSize(QSize(self.pm.profile['iconSize'],
|
||||
self.pm.profile['iconSize']))
|
||||
toggle = tb.toggleViewAction()
|
||||
toggle.setText(_("Toggle Toolbar"))
|
||||
self.connect(toggle, SIGNAL("triggered()"),
|
||||
self.onToolbarToggle)
|
||||
if not self.config['showToolbar']:
|
||||
if not self.pm.profile['showToolbar']:
|
||||
tb.hide()
|
||||
|
||||
def onToolbarToggle(self):
|
||||
tb = self.form.toolBar
|
||||
self.config['showToolbar'] = tb.isVisible()
|
||||
self.pm.profile['showToolbar'] = tb.isVisible()
|
||||
|
||||
# Dockable widgets
|
||||
##########################################################################
|
||||
|
@ -564,7 +656,7 @@ Please choose a new deck name:"""))
|
|||
# Language handling
|
||||
##########################################################################
|
||||
|
||||
def setupLang(self):
|
||||
def setupLang(self, force=None):
|
||||
"Set the user interface language."
|
||||
import locale, gettext
|
||||
import anki.lang
|
||||
|
@ -572,15 +664,16 @@ Please choose a new deck name:"""))
|
|||
locale.setlocale(locale.LC_ALL, '')
|
||||
except:
|
||||
pass
|
||||
languageDir=os.path.join(aqt.modDir, "locale")
|
||||
lang = force if force else self.pm.profile["lang"]
|
||||
languageDir=os.path.join(aqt.moduleDir, "locale")
|
||||
self.languageTrans = gettext.translation('aqt', languageDir,
|
||||
languages=[self.config["interfaceLang"]],
|
||||
languages=[lang],
|
||||
fallback=True)
|
||||
self.installTranslation()
|
||||
if getattr(self, 'form', None):
|
||||
self.form.retranslateUi(self)
|
||||
anki.lang.setLang(self.config["interfaceLang"], local=False)
|
||||
if self.config['interfaceLang'] in ("he","ar","fa"):
|
||||
anki.lang.setLang(lang, local=False)
|
||||
if lang in ("he","ar","fa"):
|
||||
self.app.setLayoutDirection(Qt.RightToLeft)
|
||||
else:
|
||||
self.app.setLayoutDirection(Qt.LeftToRight)
|
||||
|
@ -600,10 +693,8 @@ Please choose a new deck name:"""))
|
|||
##########################################################################
|
||||
|
||||
deckRelatedMenuItems = (
|
||||
"Rename",
|
||||
"Close",
|
||||
"Addcards",
|
||||
"Editdeck",
|
||||
"Add",
|
||||
"Browse",
|
||||
"Undo",
|
||||
"Export",
|
||||
"Stats",
|
||||
|
@ -647,9 +738,8 @@ Please choose a new deck name:"""))
|
|||
|
||||
def enableDeckMenuItems(self, enabled=True):
|
||||
"setEnabled deck-related items."
|
||||
for item in self.colRelatedMenuItems:
|
||||
for item in self.deckRelatedMenuItems:
|
||||
getattr(self.form, "action" + item).setEnabled(enabled)
|
||||
self.form.menuAdvanced.setEnabled(enabled)
|
||||
if not enabled:
|
||||
self.disableCardMenuItems()
|
||||
self.maybeEnableUndo()
|
||||
|
@ -697,7 +787,7 @@ Please choose a new deck name:"""))
|
|||
self.autoUpdate.start()
|
||||
|
||||
def newVerAvail(self, data):
|
||||
if self.config['suppressUpdate'] < data['latestVersion']:
|
||||
if self.pm.profile['suppressUpdate'] < data['latestVersion']:
|
||||
aqt.update.askAndUpdate(self, data)
|
||||
|
||||
def newMsg(self, data):
|
||||
|
@ -740,7 +830,7 @@ haven't been synced here yet. Continue?"""))
|
|||
# def setupMedia(self, deck):
|
||||
# print "setup media"
|
||||
# return
|
||||
# prefix = self.config['mediaLocation']
|
||||
# prefix = self.pm.profile['mediaLocation']
|
||||
# prev = deck.getVar("mediaLocation") or ""
|
||||
# # set the media prefix
|
||||
# if not prefix:
|
||||
|
@ -812,7 +902,7 @@ haven't been synced here yet. Continue?"""))
|
|||
# return p
|
||||
|
||||
# def setupDropbox(self, deck):
|
||||
# if not self.config['dropboxPublicFolder']:
|
||||
# if not self.pm.profile['dropboxPublicFolder']:
|
||||
# # put a file in the folder
|
||||
# open(os.path.join(
|
||||
# deck.mediaPrefix, "right-click-me.txt"), "w").write("")
|
||||
|
@ -836,11 +926,11 @@ haven't been synced here yet. Continue?"""))
|
|||
# That doesn't appear to be a public link. You'll be asked again when the deck \
|
||||
# is next loaded."""))
|
||||
# else:
|
||||
# self.config['dropboxPublicFolder'] = os.path.dirname(txt[0])
|
||||
# if self.config['dropboxPublicFolder']:
|
||||
# self.pm.profile['dropboxPublicFolder'] = os.path.dirname(txt[0])
|
||||
# if self.pm.profile['dropboxPublicFolder']:
|
||||
# # update media url
|
||||
# deck.setVar(
|
||||
# "mediaURL", self.config['dropboxPublicFolder'] + "/" +
|
||||
# "mediaURL", self.pm.profile['dropboxPublicFolder'] + "/" +
|
||||
# os.path.basename(deck.mediaDir()) + "/")
|
||||
|
||||
# Advanced features
|
||||
|
@ -913,11 +1003,10 @@ doubt."""))
|
|||
##########################################################################
|
||||
|
||||
def setupSystemSpecific(self):
|
||||
self.setupDocumentDir()
|
||||
addHook("macLoadEvent", self.onMacLoad)
|
||||
if isMac:
|
||||
qt_mac_set_menubar_icons(False)
|
||||
#self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
|
||||
#self.setUnifiedTitleAndToolBarOnMac(self.pm.profile['showToolbar'])
|
||||
# mac users expect a minimize option
|
||||
self.minimizeShortcut = QShortcut("Ctrl+m", self)
|
||||
self.connect(self.minimizeShortcut, SIGNAL("activated()"),
|
||||
|
@ -945,40 +1034,20 @@ doubt."""))
|
|||
def onMacLoad(self, fname):
|
||||
self.loadDeck(fname)
|
||||
|
||||
def setupDocumentDir(self):
|
||||
if self.config['documentDir']:
|
||||
return
|
||||
if isWin:
|
||||
s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
|
||||
s.beginGroup("CurrentVersion/Explorer/Shell Folders")
|
||||
d = s.value("Personal")
|
||||
if os.path.exists(d):
|
||||
d = os.path.join(d, "Anki")
|
||||
else:
|
||||
d = os.path.expanduser("~/.anki/decks")
|
||||
elif isMac:
|
||||
d = os.path.expanduser("~/Documents/Anki")
|
||||
else:
|
||||
d = os.path.expanduser("~/.anki/decks")
|
||||
try:
|
||||
os.mkdir(d)
|
||||
except (OSError, IOError):
|
||||
# already exists
|
||||
pass
|
||||
self.config['documentDir'] = d
|
||||
|
||||
# Proxy support
|
||||
##########################################################################
|
||||
|
||||
def setupProxy(self):
|
||||
print "proxy"
|
||||
return
|
||||
import urllib2
|
||||
if self.config['proxyHost']:
|
||||
if self.pm.profile['proxyHost']:
|
||||
proxy = "http://"
|
||||
if self.config['proxyUser']:
|
||||
proxy += (self.config['proxyUser'] + ":" +
|
||||
self.config['proxyPass'] + "@")
|
||||
proxy += (self.config['proxyHost'] + ":" +
|
||||
str(self.config['proxyPort']))
|
||||
if self.pm.profile['proxyUser']:
|
||||
proxy += (self.pm.profile['proxyUser'] + ":" +
|
||||
self.pm.profile['proxyPass'] + "@")
|
||||
proxy += (self.pm.profile['proxyHost'] + ":" +
|
||||
str(self.pm.profile['proxyPort']))
|
||||
os.environ["http_proxy"] = proxy
|
||||
proxy_handler = urllib2.ProxyHandler()
|
||||
opener = urllib2.build_opener(proxy_handler)
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
# - Saves in sqlite rather than a flat file so the config can't be corrupted
|
||||
|
||||
from aqt.qt import *
|
||||
import os, sys, time, random, cPickle, re
|
||||
import os, sys, time, random, cPickle, shutil
|
||||
from anki.db import DB
|
||||
from anki.utils import isMac, isWin, intTime
|
||||
from anki.utils import isMac, isWin, intTime, checksum
|
||||
|
||||
metaConf = dict(
|
||||
ver=0,
|
||||
|
@ -18,6 +18,7 @@ metaConf = dict(
|
|||
id=random.randrange(0, 2**63),
|
||||
lastMsg=-1,
|
||||
suppressUpdate=False,
|
||||
firstRun=True,
|
||||
)
|
||||
|
||||
profileConf = dict(
|
||||
|
@ -81,23 +82,35 @@ documentation for information on using a flash drive.""")
|
|||
######################################################################
|
||||
|
||||
def profiles(self):
|
||||
return [x for x in self.db.scalar("select name from profiles")
|
||||
if x != "_global"]
|
||||
return sorted(
|
||||
x for x in self.db.list("select name from profiles")
|
||||
if x != "_global")
|
||||
|
||||
def load(self, name):
|
||||
def load(self, name, passwd=None):
|
||||
prof = cPickle.loads(
|
||||
self.db.scalar("select data from profiles where name = ?", name))
|
||||
if prof['key'] and prof['key'] != self._pwhash(passwd):
|
||||
self.name = None
|
||||
raise Exception("Invalid password")
|
||||
if name != "_global":
|
||||
self.name = name
|
||||
self.idself.profile = cPickle.loads(
|
||||
self.db.scalar("select oid, data from profiles where name = ?", name))
|
||||
self.profile = prof
|
||||
|
||||
def save(self):
|
||||
sql = "update profiles set data = ? where name = ?"
|
||||
self.db.execute(sql, cPickle.dumps(self.profile), self.name)
|
||||
self.db.execute(sql, cPickle.dumps(self.meta, "_global"))
|
||||
self.db.execute(sql, cPickle.dumps(self.meta), "_global")
|
||||
self.db.commit()
|
||||
|
||||
def create(self, name):
|
||||
assert re.match("^[A-Za-z0-9 ]+$", name)
|
||||
self.db.execute("insert into profiles values (?, ?)",
|
||||
name, cPickle.dumps(profileConf))
|
||||
self.db.commit()
|
||||
|
||||
def remove(self, name):
|
||||
shutil.rmtree(self.profileFolder())
|
||||
self.db.execute("delete from profiles where name = ?", name)
|
||||
self.db.commit()
|
||||
|
||||
# Folder handling
|
||||
######################################################################
|
||||
|
@ -119,7 +132,7 @@ documentation for information on using a flash drive.""")
|
|||
######################################################################
|
||||
|
||||
def _ensureExists(self, path):
|
||||
if not exists(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
return path
|
||||
|
||||
|
@ -136,16 +149,23 @@ documentation for information on using a flash drive.""")
|
|||
|
||||
def _load(self):
|
||||
path = os.path.join(self.base, "prefs.db")
|
||||
new = not os.path.exists(path)
|
||||
self.db = DB(path, text=str)
|
||||
self.db.execute("""
|
||||
create table if not exists profiles
|
||||
(name text primary key, data text not null);""")
|
||||
try:
|
||||
self.meta = self.loadProfile("_global")
|
||||
except:
|
||||
if new:
|
||||
# create a default global profile
|
||||
self.meta = metaConf.copy()
|
||||
self.db.execute("insert into profiles values ('_global', ?)",
|
||||
cPickle.dumps(metaConf))
|
||||
# and save a default user profile for later (commits)
|
||||
self.create("User 1")
|
||||
else:
|
||||
# load previously created
|
||||
self.meta = cPickle.loads(
|
||||
self.db.scalar(
|
||||
"select data from profiles where name = '_global'"))
|
||||
|
||||
def _pwhash(self, passwd):
|
||||
return checksum(unicode(self.meta['id'])+unicode(passwd))
|
||||
|
|
|
@ -14,6 +14,8 @@ class LatestVersionFinder(QThread):
|
|||
|
||||
def __init__(self, main):
|
||||
QThread.__init__(self)
|
||||
print "autoupdate"
|
||||
return
|
||||
self.main = main
|
||||
self.config = main.config
|
||||
plat=sys.platform
|
||||
|
@ -27,6 +29,7 @@ class LatestVersionFinder(QThread):
|
|||
self.stats = d
|
||||
|
||||
def run(self):
|
||||
return
|
||||
if not self.config['checkForUpdates']:
|
||||
return
|
||||
d = self.stats
|
||||
|
|
42
aqt/utils.py
42
aqt/utils.py
|
@ -7,6 +7,14 @@ import aqt
|
|||
from anki.sound import playFromText, stripSounds
|
||||
from anki.utils import call, isWin, isMac
|
||||
|
||||
def openHelp(name):
|
||||
if "#" in name:
|
||||
name = name.split("#")
|
||||
name = name[0] + ".html#" + name[1]
|
||||
else:
|
||||
name = name + ".html"
|
||||
QDesktopServices.openUrl(QUrl(appHelpSite + name))
|
||||
|
||||
def openLink(link):
|
||||
QDesktopServices.openUrl(QUrl(link))
|
||||
|
||||
|
@ -210,7 +218,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
|
|||
assert not dir or not key
|
||||
if not dir:
|
||||
dirkey = key+"Directory"
|
||||
dir = aqt.mw.config.get(dirkey, "")
|
||||
dir = aqt.mw.pm.profile.get(dirkey, "")
|
||||
else:
|
||||
dirkey = None
|
||||
d = QFileDialog(parent)
|
||||
|
@ -226,7 +234,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
|
|||
file = unicode(list(d.selectedFiles())[0])
|
||||
if dirkey:
|
||||
dir = os.path.dirname(file)
|
||||
aqt.mw.config[dirkey] = dir
|
||||
aqt.mw.pm.profile[dirkey] = dir
|
||||
cb(file)
|
||||
d.connect(d, SIGNAL("accepted()"), accept)
|
||||
|
||||
|
@ -234,7 +242,7 @@ def getSaveFile(parent, title, dir, key, ext):
|
|||
"Ask the user for a file to save. Use DIR as config variable."
|
||||
dirkey = dir+"Directory"
|
||||
file = unicode(QFileDialog.getSaveFileName(
|
||||
parent, title, aqt.mw.config.get(dirkey, ""), key,
|
||||
parent, title, aqt.mw.pm.profile.get(dirkey, ""), key,
|
||||
None, QFileDialog.DontConfirmOverwrite))
|
||||
if file:
|
||||
# add extension
|
||||
|
@ -242,7 +250,7 @@ def getSaveFile(parent, title, dir, key, ext):
|
|||
file += ext
|
||||
# save new default
|
||||
dir = os.path.dirname(file)
|
||||
aqt.mw.config[dirkey] = dir
|
||||
aqt.mw.pm.profile[dirkey] = dir
|
||||
# check if it exists
|
||||
if os.path.exists(file):
|
||||
if not askUser(
|
||||
|
@ -253,12 +261,12 @@ def getSaveFile(parent, title, dir, key, ext):
|
|||
|
||||
def saveGeom(widget, key):
|
||||
key += "Geom"
|
||||
aqt.mw.config[key] = widget.saveGeometry()
|
||||
aqt.mw.pm.profile[key] = widget.saveGeometry()
|
||||
|
||||
def restoreGeom(widget, key, offset=None):
|
||||
key += "Geom"
|
||||
if aqt.mw.config.get(key):
|
||||
widget.restoreGeometry(aqt.mw.config[key])
|
||||
if aqt.mw.pm.profile.get(key):
|
||||
widget.restoreGeometry(aqt.mw.pm.profile[key])
|
||||
if isMac and offset:
|
||||
from aqt.main import QtConfig as q
|
||||
minor = (q.qt_version & 0x00ff00) >> 8
|
||||
|
@ -269,30 +277,30 @@ def restoreGeom(widget, key, offset=None):
|
|||
|
||||
def saveState(widget, key):
|
||||
key += "State"
|
||||
aqt.mw.config[key] = widget.saveState()
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
def restoreState(widget, key):
|
||||
key += "State"
|
||||
if aqt.mw.config.get(key):
|
||||
widget.restoreState(aqt.mw.config[key])
|
||||
if aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(aqt.mw.pm.profile[key])
|
||||
|
||||
def saveSplitter(widget, key):
|
||||
key += "Splitter"
|
||||
aqt.mw.config[key] = widget.saveState()
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
def restoreSplitter(widget, key):
|
||||
key += "Splitter"
|
||||
if aqt.mw.config.get(key):
|
||||
widget.restoreState(aqt.mw.config[key])
|
||||
if aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(aqt.mw.pm.profile[key])
|
||||
|
||||
def saveHeader(widget, key):
|
||||
key += "Header"
|
||||
aqt.mw.config[key] = widget.saveState()
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
def restoreHeader(widget, key):
|
||||
key += "Header"
|
||||
if aqt.mw.config.get(key):
|
||||
widget.restoreState(aqt.mw.config[key])
|
||||
if aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(aqt.mw.pm.profile[key])
|
||||
|
||||
def mungeQA(txt):
|
||||
txt = stripSounds(txt)
|
||||
|
@ -302,7 +310,7 @@ def mungeQA(txt):
|
|||
|
||||
def applyStyles(widget):
|
||||
try:
|
||||
styleFile = open(os.path.join(aqt.mw.config.confDir,
|
||||
styleFile = open(os.path.join(aqt.mw.pm.profile.confDir,
|
||||
"style.css"))
|
||||
widget.setStyleSheet(styleFile.read())
|
||||
except (IOError, OSError):
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>icons/blue.png</file>
|
||||
<file>icons/both.png</file>
|
||||
<file>icons/green.png</file>
|
||||
<file>icons/none.png</file>
|
||||
<file>icons/edit-find 2.png</file>
|
||||
<file>icons/edit-find-replace.png</file>
|
||||
<file>icons/graphite_smooth_folder_noncommercial.png</file>
|
||||
|
|
BIN
designer/icons/none.png
Normal file
BIN
designer/icons/none.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 358 B |
98
designer/profiles.ui
Normal file
98
designer/profiles.ui
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>352</width>
|
||||
<height>283</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Profiles</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Profile:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QListWidget" name="profiles"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="passLabel">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="passEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="login">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="delete_2">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="quit">
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in a new issue