multi-deck sync and related improvements

- deck open & browser refresh done after splash screen hidden now
- splash reduced to 3 steps
- new options sync on program load/close
- per-deck auto sync disabled on upgrade
- plugins are now always loaded before a deck has been opened
- don't prompt for sync params in auto sync, for both all and single deck
- refresh deck browser after multi sync
- wait on sync thread until syncFinished called, could fix crashes
- after a full sync, ensure interface is still disabled
- sync menu option now available in deck browser
- new option to tell a progress window it should appear immediately
This commit is contained in:
Damien Elmes 2010-07-21 13:16:07 +09:00
parent 77ede46ebb
commit f93910128f
8 changed files with 173 additions and 86 deletions

View file

@ -142,7 +142,7 @@ def run():
import forms import forms
import ui import ui
ui.splash = SplashScreen(5) ui.splash = SplashScreen(3)
import anki import anki
if anki.version != appVersion: if anki.version != appVersion:

View file

@ -99,12 +99,18 @@ class Config(dict):
'suppressEstimates': False, 'suppressEstimates': False,
'suppressUpdate': False, 'suppressUpdate': False,
'syncInMsgBox': False, 'syncInMsgBox': False,
'syncOnClose': True, 'syncOnClose': False,
'syncOnLoad': True, 'syncOnLoad': False,
'syncOnProgramClose': True,
'syncOnProgramOpen': True,
'syncPassword': "", 'syncPassword': "",
'syncUsername': "", 'syncUsername': "",
'typeAnswerFontSize': 20, 'typeAnswerFontSize': 20,
} }
# disable sync on deck load when upgrading
if not self.has_key("syncOnProgramOpen"):
self['syncOnLoad'] = False
self['syncOnClose'] = False
for (k,v) in fields.items(): for (k,v) in fields.items():
if not self.has_key(k): if not self.has_key(k):
self[k] = v self[k] = v

View file

@ -42,6 +42,7 @@ class AnkiQt(QMainWindow):
self.state = "initial" self.state = "initial"
self.hideWelcome = False self.hideWelcome = False
self.views = [] self.views = []
signal.signal(signal.SIGINT, self.onSigInt)
self.setLang() self.setLang()
self.setupStyle() self.setupStyle()
self.setupFonts() self.setupFonts()
@ -69,37 +70,40 @@ class AnkiQt(QMainWindow):
self.resize(500, 500) self.resize(500, 500)
# load deck # load deck
ui.splash.update() ui.splash.update()
if (args or self.config['loadLastDeck'] or
len(self.config['recentDeckPaths']) == 1) and \
not self.maybeLoadLastDeck(args):
self.setEnabled(True)
self.moveToState("auto")
# check for updates
ui.splash.update()
self.setupErrorHandler() self.setupErrorHandler()
self.setupMisc() self.setupMisc()
# activate & raise is useful when run from the command line on osx # activate & raise is useful when run from the command line on osx
self.activateWindow() self.activateWindow()
self.raise_() self.raise_()
# plugins might be looking at this
self.state = "noDeck"
self.loadPlugins() self.loadPlugins()
self.setupAutoUpdate() self.setupAutoUpdate()
self.rebuildPluginsMenu() self.rebuildPluginsMenu()
# run after-init hook # plugins loaded, now show interface
ui.splash.finish(self)
self.show()
# program open sync
if self.config['syncOnProgramOpen']:
self.syncDeck(interactive=False)
if (args or self.config['loadLastDeck'] or
len(self.config['recentDeckPaths']) == 1):
# open the last deck
self.maybeLoadLastDeck(args)
if self.deck:
# deck open sync?
if self.config['syncOnLoad'] and self.deck.syncName:
self.syncDeck(interactive=False)
elif not self.config['syncOnProgramOpen'] or not self.browserDecks:
# sync disabled or no user/pass, so draw deck browser manually
self.moveToState("noDeck")
# all setup is done, run after-init hook
try: try:
runHook('init') runHook('init')
except: except:
ui.utils.showWarning( ui.utils.showWarning(
_("Broken plugin:\n\n%s") % _("Broken plugin:\n\n%s") %
unicode(traceback.format_exc(), "utf-8", "replace")) unicode(traceback.format_exc(), "utf-8", "replace"))
ui.splash.update()
ui.splash.finish(self)
# ensure actions are updated after plugins loaded
self.moveToState("auto")
self.show()
if (self.deck and self.config['syncOnLoad'] and
self.deck.syncName):
self.syncDeck(interactive=False)
signal.signal(signal.SIGINT, self.onSigInt)
except: except:
ui.utils.showInfo("Error during startup:\n%s" % ui.utils.showInfo("Error during startup:\n%s" %
traceback.format_exc()) traceback.format_exc())
@ -1117,8 +1121,8 @@ your deck."""))
if not self.config['recentDeckPaths']: if not self.config['recentDeckPaths']:
return return
toRemove = [] toRemove = []
if ui.splash.finished: self.startProgress(max=len(self.config['recentDeckPaths']),
self.startProgress(max=len(self.config['recentDeckPaths'])) immediate=True)
for c, d in enumerate(self.config['recentDeckPaths']): for c, d in enumerate(self.config['recentDeckPaths']):
if ui.splash.finished: if ui.splash.finished:
self.updateProgress(_("Checking deck %(x)d of %(y)d...") % { self.updateProgress(_("Checking deck %(x)d of %(y)d...") % {
@ -1378,6 +1382,8 @@ later by using File>Close.
if not self.saveAndClose(hideWelcome=True): if not self.saveAndClose(hideWelcome=True):
event.ignore() event.ignore()
else: else:
if self.config['syncOnProgramClose']:
self.syncDeck(interactive=False)
self.prepareForExit() self.prepareForExit()
event.accept() event.accept()
self.app.quit() self.app.quit()
@ -2095,7 +2101,8 @@ it to your friends.
if not self.inMainWindow() and interactive: return if not self.inMainWindow() and interactive: return
self.setNotice() self.setNotice()
# vet input # vet input
self.ensureSyncParams() if interactive:
self.ensureSyncParams()
u=self.config['syncUsername'] u=self.config['syncUsername']
p=self.config['syncPassword'] p=self.config['syncPassword']
if not u or not p: if not u or not p:
@ -2110,28 +2117,34 @@ it to your friends.
self.deckProperties.dialog.qtabwidget.setCurrentIndex(1) self.deckProperties.dialog.qtabwidget.setCurrentIndex(1)
self.showToolTip(_("Enable syncing, choose a name, then sync again.")) self.showToolTip(_("Enable syncing, choose a name, then sync again."))
return return
if self.deck is None and self.deckPath is None: if self.deck is None and getattr(self, 'deckPath', None) is None:
# qt on linux incorrectly accepts shortcuts for disabled actions # sync all decks
return self.loadAfterSync = -1
# hide all deck-associated dialogs self.syncName = None
self.closeAllDeckWindows() self.sourcesToCheck = []
if self.deck: self.syncDecks = self.decksToSync()
# save first, so we can rollback on failure else:
self.deck.save() # sync one deck
# store data we need before closing the deck # hide all deck-associated dialogs
self.deckPath = self.deck.path self.closeAllDeckWindows()
self.syncName = self.deck.syncName or self.deck.name()
self.lastSync = self.deck.lastSync if self.deck:
if checkSources: # save first, so we can rollback on failure
self.sourcesToCheck = self.deck.s.column0( self.deck.save()
"select id from sources where syncPeriod != -1 " # store data we need before closing the deck
"and syncPeriod = 0 or :t - lastSync > syncPeriod", self.deckPath = self.deck.path
t=time.time()) self.syncName = self.deck.syncName or self.deck.name()
else: self.lastSync = self.deck.lastSync
self.sourcesToCheck = [] if checkSources:
self.deck.close() self.sourcesToCheck = self.deck.s.column0(
self.deck = None "select id from sources where syncPeriod != -1 "
self.loadAfterSync = reload "and syncPeriod = 0 or :t - lastSync > syncPeriod",
t=time.time())
else:
self.sourcesToCheck = []
self.deck.close()
self.deck = None
self.loadAfterSync = reload
# bug triggered by preferences dialog - underlying c++ widgets are not # bug triggered by preferences dialog - underlying c++ widgets are not
# garbage collected until the middle of the child thread # garbage collected until the middle of the child thread
self.state = "nostate" self.state = "nostate"
@ -2146,7 +2159,7 @@ it to your friends.
self.connect(self.syncThread, SIGNAL("noMatchingDeck"), self.selectSyncDeck) self.connect(self.syncThread, SIGNAL("noMatchingDeck"), self.selectSyncDeck)
self.connect(self.syncThread, SIGNAL("syncClockOff"), self.syncClockOff) self.connect(self.syncThread, SIGNAL("syncClockOff"), self.syncClockOff)
self.connect(self.syncThread, SIGNAL("cleanNewDeck"), self.cleanNewDeck) self.connect(self.syncThread, SIGNAL("cleanNewDeck"), self.cleanNewDeck)
self.connect(self.syncThread, SIGNAL("syncFinished"), self.syncFinished) self.connect(self.syncThread, SIGNAL("syncFinished"), self.onSyncFinished)
self.connect(self.syncThread, SIGNAL("openSyncProgress"), self.openSyncProgress) self.connect(self.syncThread, SIGNAL("openSyncProgress"), self.openSyncProgress)
self.connect(self.syncThread, SIGNAL("closeSyncProgress"), self.closeSyncProgress) self.connect(self.syncThread, SIGNAL("closeSyncProgress"), self.closeSyncProgress)
self.connect(self.syncThread, SIGNAL("updateSyncProgress"), self.updateSyncProgress) self.connect(self.syncThread, SIGNAL("updateSyncProgress"), self.updateSyncProgress)
@ -2158,16 +2171,27 @@ it to your friends.
self.syncThread.start() self.syncThread.start()
self.switchToWelcomeScreen() self.switchToWelcomeScreen()
self.setEnabled(False) self.setEnabled(False)
while not self.syncThread.isFinished(): self.syncFinished = False
while not self.syncFinished:
self.app.processEvents() self.app.processEvents()
self.syncThread.wait(100) self.syncThread.wait(100)
self.setEnabled(True) self.setEnabled(True)
return self.syncThread.ok return self.syncThread.ok
def syncFinished(self): def decksToSync(self):
ok = []
for d in self.config['recentDeckPaths']:
if os.path.exists(d):
ok.append(d)
return ok
def onSyncFinished(self):
"Reopen after sync finished." "Reopen after sync finished."
self.mainWin.buttonStack.show() self.mainWin.buttonStack.show()
if self.loadAfterSync: if self.loadAfterSync == -1:
# after sync all, so refresh browser list
self.moveToState("noDeck")
elif self.loadAfterSync:
if self.loadAfterSync == 2: if self.loadAfterSync == 2:
name = re.sub("[<>]", "", self.syncName) name = re.sub("[<>]", "", self.syncName)
p = os.path.join(self.documentDir, name + ".anki") p = os.path.join(self.documentDir, name + ".anki")
@ -2183,6 +2207,7 @@ it to your friends.
elif not self.hideWelcome: elif not self.hideWelcome:
self.moveToState("noDeck") self.moveToState("noDeck")
self.deckPath = None self.deckPath = None
self.syncFinished = True
def selectSyncDeck(self, decks, create=True): def selectSyncDeck(self, decks, create=True):
name = ui.sync.DeckChooser(self, decks, create).getName() name = ui.sync.DeckChooser(self, decks, create).getName()
@ -2195,7 +2220,7 @@ it to your friends.
if not create: if not create:
self.cleanNewDeck() self.cleanNewDeck()
else: else:
self.syncFinished() self.onSyncFinished()
def cleanNewDeck(self): def cleanNewDeck(self):
"Unload a new deck if an initial sync failed." "Unload a new deck if an initial sync failed."
@ -2213,7 +2238,7 @@ it to your friends.
_("Since this can cause many problems with syncing,\n" _("Since this can cause many problems with syncing,\n"
"syncing is disabled until you fix the problem.") "syncing is disabled until you fix the problem.")
) )
self.syncFinished() self.onSyncFinished()
def showSyncWarning(self, text): def showSyncWarning(self, text):
ui.utils.showWarning(text, self) ui.utils.showWarning(text, self)
@ -2259,6 +2284,8 @@ it to your friends.
def fullSyncFinished(self): def fullSyncFinished(self):
self.finishProgress() self.finishProgress()
# need to deactivate interface again
self.setEnabled(False)
def fullSyncProgress(self, type, val): def fullSyncProgress(self, type, val):
if type == "fromLocal": if type == "fromLocal":
@ -2277,7 +2304,6 @@ it to your friends.
"Close", "Close",
"Addcards", "Addcards",
"Editdeck", "Editdeck",
"Syncdeck",
"DisplayProperties", "DisplayProperties",
"DeckProperties", "DeckProperties",
"Undo", "Undo",
@ -2654,13 +2680,13 @@ it to your friends.
def setProgressParent(self, parent): def setProgressParent(self, parent):
self.progressParent = parent self.progressParent = parent
def startProgress(self, max=0, min=0, title=None): def startProgress(self, max=0, min=0, title=None, immediate=False):
if self.mainThread != QThread.currentThread(): if self.mainThread != QThread.currentThread():
return return
self.setBusy() self.setBusy()
if not self.progressWins: if not self.progressWins:
parent = self.progressParent or self.app.activeWindow() or self parent = self.progressParent or self.app.activeWindow() or self
p = ui.utils.ProgressWin(parent, max, min, title) p = ui.utils.ProgressWin(parent, max, min, title, immediate)
else: else:
p = None p = None
self.progressWins.append(p) self.progressWins.append(p)

View file

@ -101,6 +101,8 @@ class Preferences(QDialog):
def setupNetwork(self): def setupNetwork(self):
self.dialog.syncOnOpen.setChecked(self.config['syncOnLoad']) self.dialog.syncOnOpen.setChecked(self.config['syncOnLoad'])
self.dialog.syncOnClose.setChecked(self.config['syncOnClose']) self.dialog.syncOnClose.setChecked(self.config['syncOnClose'])
self.dialog.syncOnProgramOpen.setChecked(self.config['syncOnProgramOpen'])
self.dialog.syncOnProgramClose.setChecked(self.config['syncOnProgramClose'])
self.dialog.syncUser.setText(self.config['syncUsername']) self.dialog.syncUser.setText(self.config['syncUsername'])
self.dialog.syncPass.setText(self.config['syncPassword']) self.dialog.syncPass.setText(self.config['syncPassword'])
self.dialog.proxyHost.setText(self.config['proxyHost']) self.dialog.proxyHost.setText(self.config['proxyHost'])
@ -113,6 +115,8 @@ class Preferences(QDialog):
def updateNetwork(self): def updateNetwork(self):
self.config['syncOnLoad'] = self.dialog.syncOnOpen.isChecked() self.config['syncOnLoad'] = self.dialog.syncOnOpen.isChecked()
self.config['syncOnClose'] = self.dialog.syncOnClose.isChecked() self.config['syncOnClose'] = self.dialog.syncOnClose.isChecked()
self.config['syncOnProgramOpen'] = self.dialog.syncOnProgramOpen.isChecked()
self.config['syncOnProgramClose'] = self.dialog.syncOnProgramClose.isChecked()
self.config['syncUsername'] = unicode(self.dialog.syncUser.text()) self.config['syncUsername'] = unicode(self.dialog.syncUser.text())
self.config['syncPassword'] = unicode(self.dialog.syncPass.text()) self.config['syncPassword'] = unicode(self.dialog.syncPass.text())
self.config['proxyHost'] = unicode(self.dialog.proxyHost.text()) self.config['proxyHost'] = unicode(self.dialog.proxyHost.text())

View file

@ -10,6 +10,7 @@ from anki.sync import SyncClient, HttpSyncServerProxy, copyLocalMedia
from anki.sync import SYNC_HOST, SYNC_PORT from anki.sync import SYNC_HOST, SYNC_PORT
from anki.errors import * from anki.errors import *
from anki import DeckStorage from anki import DeckStorage
from anki.db import sqlite
import ankiqt.forms import ankiqt.forms
from anki.hooks import addHook, removeHook from anki.hooks import addHook, removeHook
@ -29,6 +30,7 @@ class Sync(QThread):
self.ok = True self.ok = True
self.onlyMerge = onlyMerge self.onlyMerge = onlyMerge
self.sourcesToCheck = sourcesToCheck self.sourcesToCheck = sourcesToCheck
self.proxy = None
addHook('fullSyncStarted', self.fullSyncStarted) addHook('fullSyncStarted', self.fullSyncStarted)
addHook('fullSyncFinished', self.fullSyncFinished) addHook('fullSyncFinished', self.fullSyncFinished)
addHook('fullSyncProgress', self.fullSyncProgress) addHook('fullSyncProgress', self.fullSyncProgress)
@ -37,7 +39,10 @@ class Sync(QThread):
self.emit(SIGNAL("setStatus"), msg, timeout) self.emit(SIGNAL("setStatus"), msg, timeout)
def run(self): def run(self):
self.syncDeck() if self.parent.syncName:
self.syncDeck()
else:
self.syncAllDecks()
removeHook('fullSyncStarted', self.fullSyncStarted) removeHook('fullSyncStarted', self.fullSyncStarted)
removeHook('fullSyncFinished', self.fullSyncFinished) removeHook('fullSyncFinished', self.fullSyncFinished)
removeHook('fullSyncProgress', self.fullSyncProgress) removeHook('fullSyncProgress', self.fullSyncProgress)
@ -77,22 +82,44 @@ class Sync(QThread):
def connect(self, *args): def connect(self, *args):
# connect, check auth # connect, check auth
proxy = HttpSyncServerProxy(self.user, self.pwd) if not self.proxy:
proxy.sourcesToCheck = self.sourcesToCheck self.setStatus(_("Connecting..."), 0)
proxy.connect("ankiqt-" + ankiqt.appVersion) proxy = HttpSyncServerProxy(self.user, self.pwd)
return proxy proxy.sourcesToCheck = self.sourcesToCheck
proxy.connect("ankiqt-" + ankiqt.appVersion)
self.proxy = proxy
return self.proxy
def syncDeck(self): def syncAllDecks(self):
self.setStatus(_("Connecting..."), 0) decks = self.parent.syncDecks
for d in decks:
self.syncDeck(deck=d)
self.emit(SIGNAL("syncFinished"))
def syncDeck(self, deck=None):
# multi-mode setup
if deck:
c = sqlite.connect(deck)
syncName = c.execute("select syncName from decks").fetchone()[0]
c.close()
if not syncName:
return
path = deck
else:
syncName = self.parent.syncName
path = self.parent.deckPath
# ensure deck mods cached
try: try:
proxy = self.connect() proxy = self.connect()
except SyncError, e: except SyncError, e:
return self.error(e) return self.error(e)
# exists on server? # exists on server?
if not proxy.hasDeck(self.parent.syncName): if not proxy.hasDeck(syncName):
if deck:
return
if self.create: if self.create:
try: try:
proxy.createDeck(self.parent.syncName) proxy.createDeck(syncName)
except SyncError, e: except SyncError, e:
return self.error(e) return self.error(e)
else: else:
@ -100,17 +127,18 @@ class Sync(QThread):
self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge) self.emit(SIGNAL("noMatchingDeck"), keys, not self.onlyMerge)
self.setStatus("") self.setStatus("")
return return
self.setStatus(_("Syncing <b>%s</b>...") % syncName, 0)
timediff = abs(proxy.timestamp - time.time()) timediff = abs(proxy.timestamp - time.time())
if timediff > 300: if timediff > 300:
self.emit(SIGNAL("syncClockOff"), timediff) self.emit(SIGNAL("syncClockOff"), timediff)
return return
# reconnect # reopen
self.deck = None self.deck = None
try: try:
self.deck = DeckStorage.Deck(self.parent.deckPath) self.deck = DeckStorage.Deck(path)
client = SyncClient(self.deck) client = SyncClient(self.deck)
client.setServer(proxy) client.setServer(proxy)
proxy.deckName = self.parent.syncName proxy.deckName = syncName
# need to do anything? # need to do anything?
start = time.time() start = time.time()
if client.prepareSync(): if client.prepareSync():
@ -129,15 +157,16 @@ class Sync(QThread):
client.fullSyncFromServer(ret[1], ret[2]) client.fullSyncFromServer(ret[1], ret[2])
self.setStatus(_("Sync complete."), 0) self.setStatus(_("Sync complete."), 0)
# reopen the deck in case we have sources # reopen the deck in case we have sources
self.deck = DeckStorage.Deck(self.parent.deckPath) self.deck = DeckStorage.Deck(path)
client.deck = self.deck client.deck = self.deck
else: else:
# diff # diff
self.setStatus(_("Determining differences..."), 0) self.setStatus(_("Determining differences..."), 0)
payload = client.genPayload(sums) payload = client.genPayload(sums)
# send payload # send payload
pr = client.payloadChangeReport(payload) if not deck:
self.setStatus("<br>" + pr + "<br>", 0) pr = client.payloadChangeReport(payload)
self.setStatus("<br>" + pr + "<br>", 0)
self.setStatus(_("Transferring payload..."), 0) self.setStatus(_("Transferring payload..."), 0)
res = client.server.applyPayload(payload) res = client.server.applyPayload(payload)
# apply reply # apply reply
@ -150,7 +179,8 @@ class Sync(QThread):
self.deck.s.commit() self.deck.s.commit()
else: else:
changes = False changes = False
self.setStatus(_("No changes found.")) if not deck:
self.setStatus(_("No changes found."))
# check sources # check sources
srcChanged = False srcChanged = False
if self.sourcesToCheck: if self.sourcesToCheck:
@ -177,12 +207,13 @@ class Sync(QThread):
self.deck.s.commit() self.deck.s.commit()
# close and send signal to main thread # close and send signal to main thread
self.deck.close() self.deck.close()
taken = time.time() - start if not deck:
if (changes or srcChanged) and taken < 2.5: taken = time.time() - start
time.sleep(2.5 - taken) if (changes or srcChanged) and taken < 2.5:
else: time.sleep(2.5 - taken)
time.sleep(0.25) else:
self.emit(SIGNAL("syncFinished")) time.sleep(0.25)
self.emit(SIGNAL("syncFinished"))
except Exception, e: except Exception, e:
self.ok = False self.ok = False
#traceback.print_exc() #traceback.print_exc()
@ -192,7 +223,8 @@ class Sync(QThread):
err = `getattr(e, 'data', None) or e` err = `getattr(e, 'data', None) or e`
self.setStatus(_("Syncing failed: %(a)s") % { self.setStatus(_("Syncing failed: %(a)s") % {
'a': err}) 'a': err})
self.error(e) if not deck:
self.error(e)
# Choosing a deck to sync to # Choosing a deck to sync to
########################################################################## ##########################################################################

View file

@ -235,7 +235,7 @@ def getBase(deck, card):
class ProgressWin(object): class ProgressWin(object):
def __init__(self, parent, max=0, min=0, title=None): def __init__(self, parent, max=0, min=0, title=None, immediate=False):
if not title: if not title:
title = "Anki" title = "Anki"
self.diag = QProgressDialog("", "", min, max, parent) self.diag = QProgressDialog("", "", min, max, parent)
@ -245,7 +245,10 @@ class ProgressWin(object):
self.diag.setAutoReset(False) self.diag.setAutoReset(False)
# qt doesn't seem to honour this consistently, and it's not triggered # qt doesn't seem to honour this consistently, and it's not triggered
# by the db progress handler, so we set it high and use maybeShow() below # by the db progress handler, so we set it high and use maybeShow() below
self.diag.setMinimumDuration(100000) if immediate:
self.diag.show()
else:
self.diag.setMinimumDuration(100000)
self.counter = min self.counter = min
self.min = min self.min = min
self.max = max self.max = max

View file

@ -2972,7 +2972,7 @@
<string>S&amp;ync</string> <string>S&amp;ync</string>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Synchronize this deck with Anki Online</string> <string>Synchronize with Anki Online</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+Shift+Y</string> <string>Ctrl+Shift+Y</string>

View file

@ -204,7 +204,7 @@
<item row="3" column="0"> <item row="3" column="0">
<widget class="QCheckBox" name="syncOnClose"> <widget class="QCheckBox" name="syncOnClose">
<property name="text"> <property name="text">
<string>Sync on close</string> <string>Sync on deck close</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -221,13 +221,27 @@
<item row="2" column="0"> <item row="2" column="0">
<widget class="QCheckBox" name="syncOnOpen"> <widget class="QCheckBox" name="syncOnOpen">
<property name="text"> <property name="text">
<string>Sync on open</string> <string>Sync on deck open</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<widget class="QCheckBox" name="syncOnProgramOpen">
<property name="text">
<string>Sync on program open</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="syncOnProgramClose">
<property name="text">
<string>Sync on program close</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -689,6 +703,8 @@
<tabstop>syncPass</tabstop> <tabstop>syncPass</tabstop>
<tabstop>syncOnOpen</tabstop> <tabstop>syncOnOpen</tabstop>
<tabstop>syncOnClose</tabstop> <tabstop>syncOnClose</tabstop>
<tabstop>syncOnProgramOpen</tabstop>
<tabstop>syncOnProgramClose</tabstop>
<tabstop>proxyHost</tabstop> <tabstop>proxyHost</tabstop>
<tabstop>proxyPort</tabstop> <tabstop>proxyPort</tabstop>
<tabstop>proxyUser</tabstop> <tabstop>proxyUser</tabstop>
@ -720,8 +736,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>270</x> <x>279</x>
<y>412</y> <y>457</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
@ -736,8 +752,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>317</x> <x>326</x>
<y>412</y> <y>457</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>