mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
revamp profile manager
- use a main window instead of a dialog, so the menu items of the main window don't appear while the profile window is active on OS X - the profile manager now has a button to automatic restoring from backup, which will prevent old backups from being clobbered - drop support for profile passwords - do the right thing when user quits from the menu in profile manager mode
This commit is contained in:
parent
059db539a7
commit
a66c5f555f
6 changed files with 205 additions and 201 deletions
|
@ -370,24 +370,26 @@ def setupApkgImport(mw, importer):
|
||||||
# adding
|
# adding
|
||||||
return True
|
return True
|
||||||
backup = re.match("backup-.*\\.apkg", base)
|
backup = re.match("backup-.*\\.apkg", base)
|
||||||
if not askUser(_("""\
|
if not mw.restoringBackup and not askUser(_("""\
|
||||||
This will delete your existing collection and replace it with the data in \
|
This will delete your existing collection and replace it with the data in \
|
||||||
the file you're importing. Are you sure?"""), msgfunc=QMessageBox.warning):
|
the file you're importing. Are you sure?"""), msgfunc=QMessageBox.warning):
|
||||||
return False
|
return False
|
||||||
# schedule replacement; don't do it immediately as we may have been
|
# schedule replacement; don't do it immediately as we may have been
|
||||||
# called as part of the startup routine
|
# called as part of the startup routine
|
||||||
mw.progress.start(immediate=True)
|
|
||||||
mw.progress.timer(
|
mw.progress.timer(
|
||||||
100, lambda mw=mw, f=importer.file: replaceWithApkg(mw, f, backup), False)
|
100, lambda mw=mw, f=importer.file: replaceWithApkg(mw, f, backup), False)
|
||||||
|
|
||||||
def replaceWithApkg(mw, file, backup):
|
def replaceWithApkg(mw, file, backup):
|
||||||
# unload collection, which will also trigger a backup
|
mw.unloadCollection(lambda: _replaceWithApkg(mw, file, backup))
|
||||||
mw.unloadCollection()
|
|
||||||
|
def _replaceWithApkg(mw, file, backup):
|
||||||
|
mw.progress.start(immediate=True)
|
||||||
# overwrite collection
|
# overwrite collection
|
||||||
z = zipfile.ZipFile(file)
|
z = zipfile.ZipFile(file)
|
||||||
try:
|
try:
|
||||||
z.extract("collection.anki2", mw.pm.profileFolder())
|
z.extract("collection.anki2", mw.pm.profileFolder())
|
||||||
except:
|
except:
|
||||||
|
mw.progress.finish()
|
||||||
showWarning(_("The provided file is not a valid .apkg file."))
|
showWarning(_("The provided file is not a valid .apkg file."))
|
||||||
return
|
return
|
||||||
# because users don't have a backup of media, it's safer to import new
|
# because users don't have a backup of media, it's safer to import new
|
||||||
|
@ -408,7 +410,9 @@ def replaceWithApkg(mw, file, backup):
|
||||||
open(dest, "wb").write(data)
|
open(dest, "wb").write(data)
|
||||||
z.close()
|
z.close()
|
||||||
# reload
|
# reload
|
||||||
mw.loadCollection()
|
if not mw.loadCollection():
|
||||||
|
mw.progress.finish()
|
||||||
|
return
|
||||||
if backup:
|
if backup:
|
||||||
mw.col.modSchema(check=False)
|
mw.col.modSchema(check=False)
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
|
|
132
aqt/main.py
132
aqt/main.py
|
@ -24,8 +24,7 @@ import aqt.stats
|
||||||
import aqt.mediasrv
|
import aqt.mediasrv
|
||||||
from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
|
from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
|
||||||
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
||||||
openHelp, openLink, checkInvalidFilename
|
openHelp, openLink, checkInvalidFilename, getFile
|
||||||
import anki.db
|
|
||||||
import sip
|
import sip
|
||||||
|
|
||||||
class AnkiQt(QMainWindow):
|
class AnkiQt(QMainWindow):
|
||||||
|
@ -87,18 +86,29 @@ class AnkiQt(QMainWindow):
|
||||||
# Profiles
|
# Profiles
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
class ProfileManager(QMainWindow):
|
||||||
|
onClose = pyqtSignal()
|
||||||
|
closeFires = True
|
||||||
|
|
||||||
|
def closeEvent(self, evt):
|
||||||
|
if self.closeFires:
|
||||||
|
self.onClose.emit()
|
||||||
|
evt.accept()
|
||||||
|
|
||||||
|
def closeWithoutQuitting(self):
|
||||||
|
self.closeFires = False
|
||||||
|
self.close()
|
||||||
|
self.closeFires = True
|
||||||
|
|
||||||
def setupProfile(self):
|
def setupProfile(self):
|
||||||
self.pendingImport = None
|
self.pendingImport = None
|
||||||
|
self.restoringBackup = False
|
||||||
# profile not provided on command line?
|
# profile not provided on command line?
|
||||||
if not self.pm.name:
|
if not self.pm.name:
|
||||||
# if there's a single profile, load it automatically
|
# if there's a single profile, load it automatically
|
||||||
profs = self.pm.profiles()
|
profs = self.pm.profiles()
|
||||||
if len(profs) == 1:
|
if len(profs) == 1:
|
||||||
try:
|
|
||||||
self.pm.load(profs[0])
|
self.pm.load(profs[0])
|
||||||
except:
|
|
||||||
# password protected
|
|
||||||
pass
|
|
||||||
if not self.pm.name:
|
if not self.pm.name:
|
||||||
self.showProfileManager()
|
self.showProfileManager()
|
||||||
else:
|
else:
|
||||||
|
@ -107,20 +117,21 @@ class AnkiQt(QMainWindow):
|
||||||
def showProfileManager(self):
|
def showProfileManager(self):
|
||||||
self.pm.profile = None
|
self.pm.profile = None
|
||||||
self.state = "profileManager"
|
self.state = "profileManager"
|
||||||
d = self.profileDiag = QDialog()
|
d = self.profileDiag = self.ProfileManager()
|
||||||
f = self.profileForm = aqt.forms.profiles.Ui_Dialog()
|
f = self.profileForm = aqt.forms.profiles.Ui_MainWindow()
|
||||||
f.setupUi(d)
|
f.setupUi(d)
|
||||||
f.login.clicked.connect(self.onOpenProfile)
|
f.login.clicked.connect(self.onOpenProfile)
|
||||||
f.profiles.itemDoubleClicked.connect(self.onOpenProfile)
|
f.profiles.itemDoubleClicked.connect(self.onOpenProfile)
|
||||||
def onQuit():
|
f.openBackup.clicked.connect(self.onOpenBackup)
|
||||||
d.close()
|
f.quit.clicked.connect(d.close)
|
||||||
self.cleanupAndExit()
|
d.onClose.connect(self.cleanupAndExit)
|
||||||
f.quit.clicked.connect(onQuit)
|
|
||||||
f.add.clicked.connect(self.onAddProfile)
|
f.add.clicked.connect(self.onAddProfile)
|
||||||
f.rename.clicked.connect(self.onRenameProfile)
|
f.rename.clicked.connect(self.onRenameProfile)
|
||||||
f.delete_2.clicked.connect(self.onRemProfile)
|
f.delete_2.clicked.connect(self.onRemProfile)
|
||||||
d.rejected.connect(d.close)
|
|
||||||
f.profiles.currentRowChanged.connect(self.onProfileRowChange)
|
f.profiles.currentRowChanged.connect(self.onProfileRowChange)
|
||||||
|
f.statusbar.setVisible(False)
|
||||||
|
# enter key opens profile
|
||||||
|
QShortcut(QKeySequence("Return"), d, activated=self.onOpenProfile)
|
||||||
self.refreshProfilesList()
|
self.refreshProfilesList()
|
||||||
# raise first, for osx testing
|
# raise first, for osx testing
|
||||||
d.show()
|
d.show()
|
||||||
|
@ -142,22 +153,14 @@ class AnkiQt(QMainWindow):
|
||||||
return
|
return
|
||||||
name = self.pm.profiles()[n]
|
name = self.pm.profiles()[n]
|
||||||
f = self.profileForm
|
f = self.profileForm
|
||||||
passwd = not self.pm.load(name)
|
self.pm.load(name)
|
||||||
f.passEdit.setVisible(passwd)
|
|
||||||
f.passLabel.setVisible(passwd)
|
|
||||||
|
|
||||||
def openProfile(self):
|
def openProfile(self):
|
||||||
name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
|
name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
|
||||||
passwd = self.profileForm.passEdit.text()
|
return self.pm.load(name)
|
||||||
return self.pm.load(name, passwd)
|
|
||||||
|
|
||||||
def onOpenProfile(self):
|
def onOpenProfile(self):
|
||||||
if not self.openProfile():
|
self.loadProfile(self.profileDiag.closeWithoutQuitting)
|
||||||
showWarning(_("Invalid password."))
|
|
||||||
return
|
|
||||||
self.profileDiag.close()
|
|
||||||
self.loadProfile()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def profileNameOk(self, str):
|
def profileNameOk(self, str):
|
||||||
return not checkInvalidFilename(str)
|
return not checkInvalidFilename(str)
|
||||||
|
@ -176,8 +179,6 @@ class AnkiQt(QMainWindow):
|
||||||
|
|
||||||
def onRenameProfile(self):
|
def onRenameProfile(self):
|
||||||
name = getOnlyText(_("New name:"), default=self.pm.name)
|
name = getOnlyText(_("New name:"), default=self.pm.name)
|
||||||
if not self.openProfile():
|
|
||||||
return showWarning(_("Invalid password."))
|
|
||||||
if not name:
|
if not name:
|
||||||
return
|
return
|
||||||
if name == self.pm.name:
|
if name == self.pm.name:
|
||||||
|
@ -193,18 +194,43 @@ class AnkiQt(QMainWindow):
|
||||||
profs = self.pm.profiles()
|
profs = self.pm.profiles()
|
||||||
if len(profs) < 2:
|
if len(profs) < 2:
|
||||||
return showWarning(_("There must be at least one profile."))
|
return showWarning(_("There must be at least one profile."))
|
||||||
# password correct?
|
|
||||||
if not self.openProfile():
|
|
||||||
return
|
|
||||||
# sure?
|
# sure?
|
||||||
if not askUser(_("""\
|
if not askUser(_("""\
|
||||||
All cards, notes, and media for this profile will be deleted. \
|
All cards, notes, and media for this profile will be deleted. \
|
||||||
Are you sure?""")):
|
Are you sure?"""), msgfunc=QMessageBox.warning, defaultno=True):
|
||||||
return
|
return
|
||||||
self.pm.remove(self.pm.name)
|
self.pm.remove(self.pm.name)
|
||||||
self.refreshProfilesList()
|
self.refreshProfilesList()
|
||||||
|
|
||||||
def loadProfile(self):
|
def onOpenBackup(self):
|
||||||
|
if not askUser(_("""\
|
||||||
|
Replace your collection with an earlier backup?"""),
|
||||||
|
msgfunc=QMessageBox.warning,
|
||||||
|
defaultno=True):
|
||||||
|
return
|
||||||
|
def doOpen(path):
|
||||||
|
self._openBackup(path)
|
||||||
|
getFile(self.profileDiag, _("Revert to backup"),
|
||||||
|
cb=doOpen, filter="*.apkg", dir=self.pm.backupFolder())
|
||||||
|
|
||||||
|
def _openBackup(self, path):
|
||||||
|
try:
|
||||||
|
# move the existing collection to the trash, as it may not open
|
||||||
|
self.pm.trashCollection()
|
||||||
|
except:
|
||||||
|
showWarning(_("Unable to move existing file to trash - please try restarting your computer."))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.pendingImport = path
|
||||||
|
self.restoringBackup = True
|
||||||
|
|
||||||
|
showInfo(_("""\
|
||||||
|
Automatic syncing and backups have been disabled while restoring. To enable them again, \
|
||||||
|
close the profile or restart Anki."""))
|
||||||
|
|
||||||
|
self.onOpenProfile()
|
||||||
|
|
||||||
|
def loadProfile(self, onsuccess=None):
|
||||||
self.maybeAutoSync()
|
self.maybeAutoSync()
|
||||||
|
|
||||||
if not self.loadCollection():
|
if not self.loadCollection():
|
||||||
|
@ -223,14 +249,11 @@ Are you sure?""")):
|
||||||
|
|
||||||
# import pending?
|
# import pending?
|
||||||
if self.pendingImport:
|
if self.pendingImport:
|
||||||
if self.pm.profile['key']:
|
|
||||||
showInfo(_("""\
|
|
||||||
To import into a password protected profile, please open the profile before attempting to import."""))
|
|
||||||
else:
|
|
||||||
self.handleImport(self.pendingImport)
|
self.handleImport(self.pendingImport)
|
||||||
|
|
||||||
self.pendingImport = None
|
self.pendingImport = None
|
||||||
runHook("profileLoaded")
|
runHook("profileLoaded")
|
||||||
|
if onsuccess:
|
||||||
|
onsuccess()
|
||||||
|
|
||||||
def unloadProfile(self, onsuccess):
|
def unloadProfile(self, onsuccess):
|
||||||
def callback():
|
def callback():
|
||||||
|
@ -246,12 +269,13 @@ To import into a password protected profile, please open the profile before atte
|
||||||
self.pm.save()
|
self.pm.save()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
|
self.restoringBackup = False
|
||||||
|
|
||||||
# at this point there should be no windows left
|
# at this point there should be no windows left
|
||||||
self._checkForUnclosedWidgets()
|
self._checkForUnclosedWidgets()
|
||||||
|
|
||||||
self.maybeAutoSync()
|
self.maybeAutoSync()
|
||||||
|
|
||||||
self.pm.profile = None
|
|
||||||
|
|
||||||
def _checkForUnclosedWidgets(self):
|
def _checkForUnclosedWidgets(self):
|
||||||
for w in self.app.topLevelWidgets():
|
for w in self.app.topLevelWidgets():
|
||||||
|
@ -279,8 +303,8 @@ To import into a password protected profile, please open the profile before atte
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(_("""\
|
showWarning(_("""\
|
||||||
Anki was unable to open your collection file. If problems persist after \
|
Anki was unable to open your collection file. If problems persist after \
|
||||||
restarting your computer, please see the manual for how to restore from \
|
restarting your computer, please use the Open Backup button in the profile \
|
||||||
an automatic backup.
|
manager.
|
||||||
|
|
||||||
Debug info:
|
Debug info:
|
||||||
""")+traceback.format_exc())
|
""")+traceback.format_exc())
|
||||||
|
@ -300,7 +324,13 @@ Debug info:
|
||||||
self.closeAllWindows(callback)
|
self.closeAllWindows(callback)
|
||||||
|
|
||||||
def _unloadCollection(self):
|
def _unloadCollection(self):
|
||||||
self.progress.start(label=_("Backing Up..."), immediate=True)
|
if not self.col:
|
||||||
|
return
|
||||||
|
if self.restoringBackup:
|
||||||
|
label = _("Closing...")
|
||||||
|
else:
|
||||||
|
label = _("Backing Up...")
|
||||||
|
self.progress.start(label=label, immediate=True)
|
||||||
corrupt = False
|
corrupt = False
|
||||||
try:
|
try:
|
||||||
self.maybeOptimize()
|
self.maybeOptimize()
|
||||||
|
@ -315,11 +345,11 @@ when the collection is stored on a network or cloud drive. Please see \
|
||||||
the manual for information on how to restore from an automatic backup."))
|
the manual for information on how to restore from an automatic backup."))
|
||||||
try:
|
try:
|
||||||
self.col.close()
|
self.col.close()
|
||||||
except:
|
finally:
|
||||||
pass
|
|
||||||
self.col = None
|
self.col = None
|
||||||
if not corrupt:
|
if not corrupt and not self.restoringBackup:
|
||||||
self.backup()
|
self.backup()
|
||||||
|
|
||||||
self.progress.finish()
|
self.progress.finish()
|
||||||
|
|
||||||
# Backup and auto-optimize
|
# Backup and auto-optimize
|
||||||
|
@ -534,7 +564,7 @@ title="%s" %s>%s</button>''' % (
|
||||||
self.form.centralwidget.setLayout(self.mainLayout)
|
self.form.centralwidget.setLayout(self.mainLayout)
|
||||||
|
|
||||||
def closeAllWindows(self, onsuccess):
|
def closeAllWindows(self, onsuccess):
|
||||||
return aqt.dialogs.closeAll(onsuccess)
|
aqt.dialogs.closeAll(onsuccess)
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -544,6 +574,7 @@ title="%s" %s>%s</button>''' % (
|
||||||
|
|
||||||
def onSigInt(self, signum, frame):
|
def onSigInt(self, signum, frame):
|
||||||
# interrupt any current transaction and schedule a rollback & quit
|
# interrupt any current transaction and schedule a rollback & quit
|
||||||
|
if self.col:
|
||||||
self.col.db.interrupt()
|
self.col.db.interrupt()
|
||||||
def quit():
|
def quit():
|
||||||
self.col.db.rollback()
|
self.col.db.rollback()
|
||||||
|
@ -596,10 +627,8 @@ title="%s" %s>%s</button>''' % (
|
||||||
def maybeAutoSync(self):
|
def maybeAutoSync(self):
|
||||||
if (not self.pm.profile['syncKey']
|
if (not self.pm.profile['syncKey']
|
||||||
or not self.pm.profile['autoSync']
|
or not self.pm.profile['autoSync']
|
||||||
or self.safeMode):
|
or self.safeMode
|
||||||
return
|
or self.restoringBackup):
|
||||||
if self.pendingImport and os.path.basename(
|
|
||||||
self.pendingImport).startswith("backup-"):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# ok to sync
|
# ok to sync
|
||||||
|
@ -669,7 +698,12 @@ title="%s" %s>%s</button>''' % (
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"User hit the X button, etc."
|
if self.state == "profileManager":
|
||||||
|
# if profile manager active, this event may fire via OS X menu bar's
|
||||||
|
# quit option
|
||||||
|
self.profileDiag.close()
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
# ignore the event for now, as we need time to clean up
|
# ignore the event for now, as we need time to clean up
|
||||||
event.ignore()
|
event.ignore()
|
||||||
self.unloadProfileAndExit()
|
self.unloadProfileAndExit()
|
||||||
|
|
|
@ -150,22 +150,7 @@ Not currently enabled; click the sync button in the main window to enable."""))
|
||||||
|
|
||||||
def setupOptions(self):
|
def setupOptions(self):
|
||||||
self.form.pastePNG.setChecked(self.prof.get("pastePNG", False))
|
self.form.pastePNG.setChecked(self.prof.get("pastePNG", False))
|
||||||
self.form.profilePass.clicked.connect(self.onProfilePass)
|
|
||||||
|
|
||||||
def updateOptions(self):
|
def updateOptions(self):
|
||||||
self.prof['pastePNG'] = self.form.pastePNG.isChecked()
|
self.prof['pastePNG'] = self.form.pastePNG.isChecked()
|
||||||
|
|
||||||
def onProfilePass(self):
|
|
||||||
pw, ret = getText(_("""\
|
|
||||||
Lock account with password, or leave blank:"""))
|
|
||||||
if not ret:
|
|
||||||
return
|
|
||||||
if not pw:
|
|
||||||
self.prof['key'] = None
|
|
||||||
return
|
|
||||||
pw2, ret = getText(_("Confirm password:"))
|
|
||||||
if not ret:
|
|
||||||
return
|
|
||||||
if pw != pw2:
|
|
||||||
showWarning(_("Passwords didn't match"))
|
|
||||||
self.prof['key'] = self.mw.pm._pwhash(pw)
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ metaConf = dict(
|
||||||
|
|
||||||
profileConf = dict(
|
profileConf = dict(
|
||||||
# profile
|
# profile
|
||||||
key=None,
|
|
||||||
mainWindowGeom=None,
|
mainWindowGeom=None,
|
||||||
mainWindowState=None,
|
mainWindowState=None,
|
||||||
numBackups=30,
|
numBackups=30,
|
||||||
|
@ -133,13 +132,10 @@ a flash drive.""" % self.base)
|
||||||
self.db.list("select name from profiles")
|
self.db.list("select name from profiles")
|
||||||
if x != "_global")
|
if x != "_global")
|
||||||
|
|
||||||
def load(self, name, passwd=None):
|
def load(self, name):
|
||||||
data = self.db.scalar("select cast(data as blob) from profiles where name = ?", name)
|
data = self.db.scalar("select cast(data as blob) from profiles where name = ?", name)
|
||||||
# some profiles created in python2 may not decode properly
|
# some profiles created in python2 may not decode properly
|
||||||
prof = pickle.loads(data, errors="ignore")
|
prof = pickle.loads(data, errors="ignore")
|
||||||
if prof['key'] and prof['key'] != self._pwhash(passwd):
|
|
||||||
self.name = None
|
|
||||||
return False
|
|
||||||
if name != "_global":
|
if name != "_global":
|
||||||
self.name = name
|
self.name = name
|
||||||
self.profile = prof
|
self.profile = prof
|
||||||
|
@ -164,6 +160,11 @@ a flash drive.""" % self.base)
|
||||||
self.db.execute("delete from profiles where name = ?", name)
|
self.db.execute("delete from profiles where name = ?", name)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
def trashCollection(self):
|
||||||
|
p = self.collectionPath()
|
||||||
|
if os.path.exists(p):
|
||||||
|
send2trash(p)
|
||||||
|
|
||||||
def rename(self, name):
|
def rename(self, name):
|
||||||
oldName = self.name
|
oldName = self.name
|
||||||
oldFolder = self.profileFolder()
|
oldFolder = self.profileFolder()
|
||||||
|
@ -319,9 +320,6 @@ please see:
|
||||||
%s
|
%s
|
||||||
""") % (appHelpSite + "#startupopts"))
|
""") % (appHelpSite + "#startupopts"))
|
||||||
|
|
||||||
def _pwhash(self, passwd):
|
|
||||||
return checksum(str(self.meta['id'])+str(passwd))
|
|
||||||
|
|
||||||
# Default language
|
# Default language
|
||||||
######################################################################
|
######################################################################
|
||||||
# On first run, allow the user to choose the default language
|
# On first run, allow the user to choose the default language
|
||||||
|
|
|
@ -184,16 +184,6 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="profilePass">
|
|
||||||
<property name="text">
|
|
||||||
<string>Profile Password...</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab_2">
|
<widget class="QWidget" name="tab_2">
|
||||||
|
@ -429,7 +419,6 @@
|
||||||
<tabstop>dayOffset</tabstop>
|
<tabstop>dayOffset</tabstop>
|
||||||
<tabstop>lrnCutoff</tabstop>
|
<tabstop>lrnCutoff</tabstop>
|
||||||
<tabstop>timeLimit</tabstop>
|
<tabstop>timeLimit</tabstop>
|
||||||
<tabstop>profilePass</tabstop>
|
|
||||||
<tabstop>syncMedia</tabstop>
|
<tabstop>syncMedia</tabstop>
|
||||||
<tabstop>syncOnProgramOpen</tabstop>
|
<tabstop>syncOnProgramOpen</tabstop>
|
||||||
<tabstop>syncDeauth</tabstop>
|
<tabstop>syncDeauth</tabstop>
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>Dialog</class>
|
<class>MainWindow</class>
|
||||||
<widget class="QDialog" name="Dialog">
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>352</width>
|
<width>423</width>
|
||||||
<height>283</height>
|
<height>356</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Profiles</string>
|
<string>Profiles</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/anki.png</normaloff>:/icons/anki.png</iconset>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Profile:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -32,20 +22,6 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QListWidget" name="profiles"/>
|
<widget class="QListWidget" name="profiles"/>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -55,6 +31,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Open</string>
|
<string>Open</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -98,23 +77,38 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="openBackup">
|
||||||
|
<property name="text">
|
||||||
|
<string>Open Backup...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<widget class="QMenuBar" name="menubar">
|
||||||
<tabstop>profiles</tabstop>
|
<property name="enabled">
|
||||||
<tabstop>passEdit</tabstop>
|
<bool>false</bool>
|
||||||
<tabstop>login</tabstop>
|
</property>
|
||||||
<tabstop>add</tabstop>
|
<property name="geometry">
|
||||||
<tabstop>rename</tabstop>
|
<rect>
|
||||||
<tabstop>delete_2</tabstop>
|
<x>0</x>
|
||||||
<tabstop>quit</tabstop>
|
<y>0</y>
|
||||||
</tabstops>
|
<width>423</width>
|
||||||
<resources>
|
<height>22</height>
|
||||||
<include location="icons.qrc"/>
|
</rect>
|
||||||
</resources>
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
Loading…
Reference in a new issue