-
Notifications
You must be signed in to change notification settings - Fork 0
/
settings_ui_actions.py
1631 lines (1383 loc) · 79.9 KB
/
settings_ui_actions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# wanted to move all the ui functions into their own file to make everything look nicer
# otherwise the settings_ui.py file was going to get really crowded.
import re
import cPickle
import threading
import socket
from collections import OrderedDict as OD
from numbers import Number
#from collections import namedtuple as NT
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtCore import QDir
from PyQt4.QtCore import SIGNAL
from copy import deepcopy as DC
from ntpath import basename as ntpath_basename
from urllib import urlopen
from ast import literal_eval as safe_eval
from tempfile import gettempdir
from os import sep as OS_SEP
from time import sleep, time
from net_options_dialog import Ui_netOptionsDialog
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
#I didnt think this should be included in each guiActions instance
def regexValidator(expr):
#This function will take in a string, as expr, and determine if it is a valid regular expression
#Super simple
try:
re.compile(expr)
return True
except:
return False
class Client(threading.Thread):
def __init__(self, guiActionsReference, address, remote_connect):
self.address = address
if remote_connect == False:
self.address = ["127.0.0.1", 0]
self.remote_connect = remote_connect
self.quitting = False
self.recv_tries = 0
self.connected = False
self.data_thread = None
self.waiting = False
self.SEND_WAIT = 1
self.last_send = int(time()) - self.SEND_WAIT
self.CONNECT_WAIT = 5
self.last_connect_try = int(time()) - self.CONNECT_WAIT
self.gref = guiActionsReference
self.MW = self.gref.context.MainWindow
#Set our connection status to False for now
self.MW.emit(SIGNAL("gotScriptStatusUpdate"), "None", False)
self.main_socket = None
self.DATA_WAIT_TIMEOUT = 1
super(Client, self).__init__()
def quit_thread(self):
self.quitting = True
try:
self.main_socket.send("CONNECTION_CLOSING")
except:
pass
try:
self.main_socket.close()
except:
pass
def update_address(self, new_address, remote_connect):
if remote_connect == True:
self.address = new_address
else:
self.address = ["127.0.0.1", 0]
self.remote_connect = remote_connect
try:
self.main_socket.send("CONNECTION_CLOSING")
except:
pass
try:
self.main_socket.close()
except:
pass
self.connected = False
self.connection = None
def get_connection(self):
while self.connected is False and self.quitting is False:
if int(time()) - self.last_connect_try > self.CONNECT_WAIT:
self.last_connect_try = int(time())
try:
if self.remote_connect is False:
self.address[1] = self.gref.get_current_port()
if self.address[1] is None: #Failed to get port from file
continue
self.main_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.main_socket.settimeout(self.DATA_WAIT_TIMEOUT)
self.main_socket.connect(tuple(self.address))
self.connected = True
continue
except:
pass
sleep(0.5)
if self.quitting is True:
self.connected = False
def get_script_status(self):
if self.connected is True and self.quitting is False:
try:
self.main_socket.send("GET_SCRIPT_STATUS")
self.waiting = True
except Exception as e:
if "timed out" not in str(e):
self.main_socket.close()
self.connected = False
self.waiting = False
return
return
def pass_script_cmd(self, cmd):
if self.connected is True and self.quitting is False and len(cmd) > 0:
try:
self.main_socket.send(cmd)
#Wait for receive confirm
self.waiting = True
except Exception as e:
if "timed out" not in str(e):
self.main_socket.close()
self.connected = False
self.waiting = False
return
def get_return_data(self):
if self.connected is True and self.quitting is False and self.recv_tries < 5:
try:
rawdata = self.main_socket.recv(8192)
#Do we still need this try now that we've removed the re.search().groups() that would have raised exception if it failed?
#I mean its still possible I guess that if rawdata returns something other than a string it could fall through here again.
#Would I rather do an isinstance() in an if statement or keep the try?
try:
rawdata = rawdata.replace("\\n", "\n")
rawdata = rawdata.replace("\\\\", "\\")
except:
self.waiting = False
self.recv_tries = 0
return None
try:
data = cPickle.loads(rawdata)
except:
return None
#End our receive operation
self.waiting = False
self.recv_tries = 0
return data
except Exception as e:
if "timed out" not in str(e):
self.main_socket.close()
self.connected = False
self.waiting = False
return
self.recv_tries += 1
return None
else:
self.waiting = False #Too many tries, we disconnected, or caught the quit signal.
self.recv_tries = 0
return None
def run(self):
while self.quitting is False:
sleep(0.2) #Limit how fast we run this loop
#Check connection, try to get it back if we lost it
if self.connected is False:
#Update our status to disconnected:
self.MW.emit(SIGNAL("gotScriptStatusUpdate"), "DISCON", False)
self.get_connection()
continue
#Request data
if self.waiting is False:
if int(time()) - self.last_send > self.SEND_WAIT:
#data = None
#data = self.get_script_status()
self.get_script_status()
self.last_send = int(time())
continue
#Waiting for data, lets get it
if self.waiting is True:
data = self.get_return_data()
if data is not None:
if data == "CONNECTION_CLOSING":
self.main_socket.close()
self.connected = False
continue
if isinstance(data, basestring) is True:
continue
self.MW.emit(SIGNAL("gotScriptStatusUpdate"), data, True)
continue
sleep(1) #Can we ever even get here?
try:
self.main_socket.close()
except:
pass
return
class guiActions(object):
def __init__(self, context):
self.context = context
self.client_thread = None
#this is different from self.context.SettingsManager.isLoaded.
#This just flags during the load operation itself and gives no indication as to whether or not something is currently loaded.
self.__is_loading = False
self.script_status_vars = self.context.SettingsManager.scriptStatusDefaults
self.network_state = 1
self.remote_network_address = ""
self.remote_network_port = ""
def setLabelAndColor(self, element, status):
if str(status).lower() == "on":
stylesheet = "<html><head/><body><p align=\"center\"><span style=\" font-weight:600; color:#00c800;\">On</span></p></body></html>"
elif str(status).lower() == "off" or str(status).lower() == "Not Running" or str(status).lower() == "n/a":
stylesheet = "<html><head/><body><p align=\"center\"><span style=\" font-weight:600; color:#ff0000;\">Off</span></p></body></html>"
else:
stylesheet = "<html><head/><body><p align=\"center\"><span style=\" font-weight:600; color:#000000;\">%s</span></p></body></html>" % (status)
#Now set the element with its data
element.setText(stylesheet)
def toggleScriptAutodl(self):
self.client_thread.pass_script_cmd("TOGGLE_AUTODL")
def reloadScriptIniFile(self):
self.client_thread.pass_script_cmd("RELOAD_SCRIPT_SETTINGS")
def get_current_port(self):
tmpname = gettempdir() + OS_SEP + "sccw_port.txt"
try:
tempfile = open(tmpname, 'r')
portNum = tempfile.read()
tempfile.close()
if len(portNum) > 1:
return int(portNum)
else:
return None
except:
return None
def updateScriptStatusCallback(self, script_status, script_connected=True):
#Our returned data from the script should be at least the size of our default status
if isinstance(script_status, dict) is False or len(script_status) < len(self.context.SettingsManager.scriptStatusDefaults):
script_status = self.context.SettingsManager.scriptStatusDefaults
self.script_status_vars = script_status
#Now we update the main page with our data
self.setLabelAndColor(self.context.ssVersionState, script_status["version"])
self.setLabelAndColor(self.context.ssStatusState, script_status["autodlstatus"])
self.setLabelAndColor(self.context.ssSSLDownloadState, script_status["ssl"])
self.setLabelAndColor(self.context.ssMaxTriesState, script_status["max_dl_tries"])
self.setLabelAndColor(self.context.ssRetryDelayState, script_status["retry_wait"])
self.setLabelAndColor(self.context.ssCloudflareState, script_status["cf_workaround"])
self.setLabelAndColor(self.context.ssDupecheckingState, script_status["dupecheck"])
self.setLabelAndColor(self.context.ssLoggingState, script_status["logging"])
self.setLabelAndColor(self.context.ssVerboseState, script_status["verbose"])
self.setLabelAndColor(self.context.ssRecentState, script_status["recent_list_size"])
self.setLabelAndColor(self.context.ssWatchAvoidState, script_status["wl_al_size"])
#And enable or disable our control group if we have a good connection
if script_connected is True:
self.context.scButtonFrame.setEnabled(True)
control_status_html = "<html><head/><body><p><span style=\" color:#00c800;\">Connected</span></p></body></html>"
else:
self.context.scButtonFrame.setEnabled(False)
control_status_html = "<html><head/><body><p><span style=\" color:#ff0000;\">Not Connected</span></p></body></html>"
self.context.sccsConStatusState.setText(_translate("sccw_SettingsUI", control_status_html, None))
def setNetworkOptions(self, optiondict):
#Check if we have a good port, otherwise dont update
try:
int(optiondict["port"])
except:
return
self.network_state = optiondict["state"]
self.remote_network_address = optiondict["address"]
self.remote_network_port = optiondict["port"]
#Update the client thread with the new address
self.client_thread.update_address([self.remote_network_address, int(self.remote_network_port)], bool(int(self.network_state)^1))
#If we are a remote connect, then disable the edit scc2.ini button, otherwise make sure its enabled
if int(self.network_state) == 0:
self.context.scbfEditCurIniButton.setEnabled(False)
else:
self.context.scbfEditCurIniButton.setEnabled(True)
def openNetworkSettingsDialog(self):
#Create and open our dialog window
netOptsDialog = QtGui.QDialog()
mQDialog = Ui_netOptionsDialog()
current_state = {}
current_state["state"] = self.network_state
current_state["address"] = self.remote_network_address
current_state["port"] = self.remote_network_port
mQDialog.setupUi(netOptsDialog, self, current_state)
netOptsDialog.exec_() #modal exec?
def startClientThread(self):
self.client_thread = Client(self, ["127.0.0.1", 0], False)
self.client_thread.start()
def loadActiveIni(self):
if len(self.script_status_vars["ini_path"]) > 1:
self.loadUiState(dd_filename=self.script_status_vars["ini_path"])
def checkRegexContent(self, pElement, pCheckbox):
#This is a generic function for each box supporting regular expressions.
#You can just connect the finishedEditing() signal to this function, using partial to fill in the variables.
pCheckbox_value = pCheckbox.checkState()
regex = str(pElement.text())
#We only operate if the regex checkbox is active
if pCheckbox_value > 0:
if regexValidator(regex) is True:
#Regular expression checks out, we will set the background back to normal
pElement.setStyleSheet("QLineEdit { background: rgb(255, 255, 255); }")
else:
#invalid regex, set the background to orange to indicate the error
pElement.setStyleSheet("QLineEdit { background: rgb(255, 100, 0); }")
else:
#Set the background to normal, just in-case it was set before
pElement.setStyleSheet("QLineEdit { background: rgb(255, 255, 255); }")
def get_multi(self, sizedetail):
sizedetail = str(sizedetail).upper()
if sizedetail == "KB":
multi=int(1024)
elif sizedetail == "MB":
multi=int(1048576)
elif sizedetail == "GB":
multi=int(1073741824)
else:
multi = int(1)
return multi
def checkSizeLimitBounds(self, tab):
#tab should be either gen or wlist
#this function will make sure the upper and lower are within bounds of each other.
ul_set = {}
ul_set["gen"] = {}
ul_set["gen"]["lower"] = [self.context.globalSizeLimitLowerTextbox, self.context.globalSizeLimitLowerSuffixSelector]
ul_set["gen"]["upper"] = [self.context.globalSizeLimitUpperTextbox, self.context.globalSizeLimitUpperSuffixSelector]
ul_set["wlist"] = {}
ul_set["wlist"]["lower"] = [self.context.WLSGsizeLimitLowerTextbox, self.context.WLSGsizeLimitLowerSuffixSelector]
ul_set["wlist"]["upper"] = [self.context.WLSGsizeLimitUpperTextbox, self.context.WLSGsizeLimitUpperSuffixSelector]
#Reset to plain white first off, we only want to change color when we know for sure the numbers dont match.
ul_set[tab]["lower"][0].setStyleSheet("QLineEdit { background: rgb(255, 255, 255); }")
ul_set[tab]["upper"][0].setStyleSheet("QLineEdit { background: rgb(255, 255, 255); }")
#Sanity checks, we dont want to do anything unless both boxes have integers in them
try:
int(ul_set[tab]["lower"][0].text())
int(ul_set[tab]["upper"][0].text())
except:
return
#First thing we need to do is convert both the upper and lower numbers to bytes
upper_multi = self.get_multi(self.convertIndex(ul_set[tab]["upper"][1].currentIndex()))
lower_multi = self.get_multi(self.convertIndex(ul_set[tab]["lower"][1].currentIndex()))
upper_size_bytes = int(ul_set[tab]["upper"][0].text()) * upper_multi
lower_size_bytes = int(ul_set[tab]["lower"][0].text()) * lower_multi
#Now its a simple compare to see which is bigger
if upper_size_bytes < lower_size_bytes:
#We got a prob, change the background so we know
ul_set[tab]["lower"][0].setStyleSheet("QLineEdit { background: rgb(255, 100, 0); }")
ul_set[tab]["upper"][0].setStyleSheet("QLineEdit { background: rgb(255, 100, 0); }")
def updateUiTitle(self, text):
text = "SCCwatcher - %s" % (text)
self.context.MainWindow.setWindowTitle(_translate("MainWindow", text, None))
def newSettingsFile(self):
#Start fresh, make sure the user is ok with that
has_changed, rtn = self.checkUiStateChange()
#If there were changes, check how the user wanted us to proceed
if has_changed is True:
if rtn == QtGui.QMessageBox.Save:
if self.saveUiToFile() is False:
return False
elif rtn == QtGui.QMessageBox.Discard:
pass
elif rtn == QtGui.QMessageBox.Cancel:
return False
#Now we clear the UI
self.clearGUIState()
#Just for some other logic stuffs
return True
def clearGUIState(self):
self.clearUiData(self.context.SettingsManager.guiDefaults["allOtherDefaults"])
self.clearUiData(self.context.SettingsManager.guiDefaults["watchlistDefaults"])
self.clearUiData(self.context.SettingsManager.guiDefaults["avoidlistDefaults"])
#Remove any watches or avoids
self.clearList(self.context.WLGwatchlistItemsList)
self.clearList(self.context.avoidlistItemsList)
#Reset internal state tracking
self.context.SettingsManager.guiState["globalOptionsState"] = DC(self.context.SettingsManager.guiDefaults["allOtherDefaults"])
self.context.SettingsManager.guiState["watchlistState"] = OD()
self.context.SettingsManager.guiState["avoidlistState"] = OD()
#Close the current file completely
self.context.SettingsManager.closeSettingsFile()
#Clear redo/undo stacks
self.context.undoRedoSystem.reset()
#Now set the title
self.updateUiTitle("New Settings File")
#and finally set the UI state to fresh
self.updateUiStateInfo()
def checkListChanges(self, list_object, check_dict):
#This function will take a watchlist object as list_object and compare its items against the data stored in check_dict
if len(check_dict) == list_object.count():
#Ok well at least the size matches, now onto the rest
#Now loop over the items in the list_object and check for differences one by one. (is there a better way?)
for cur_index in xrange(0, list_object.count()):
cur_item = list_object.item(cur_index)
if cur_item == 0: #Could this cause sccwatcher to think a file has no changes when it does, or vice versa, because an item was skipped?
continue
cur_item_title = str(cur_item.text())
#Do a check to see if the title is in our saved data.
#If not, we know something has changed and we can quit right now
if cur_item_title not in check_dict.keys():
return True
#So this name exists in the check_dict dict, we have to check every option now.
cur_item_data = cur_item.data(Qt.UserRole).toPyObject()
for cid_option, cid_value in cur_item_data.iteritems():
try:
check_dict[cur_item_title][cid_option]
except:
if cid_value != "" and cid_value != 0 and str(cid_value) != "0":
return True
if str(cid_value) != str(check_dict[cur_item_title][cid_option]):
return True
else:
#Size is wrong so we know its not the same.
return True
def checkUiStateChange(self):
#This function checks to see if the GUI's current state matches the last saved state. If it matches, it returns true. If it doesnt match it returns false.
is_changed = False
globalOptions = self.context.SettingsManager.guiState["globalOptionsState"]
wlOptions = self.context.SettingsManager.guiState["watchlistState"]
alOptions = self.context.SettingsManager.guiState["avoidlistState"]
watchlist = self.context.WLGwatchlistItemsList
avoidlist = self.context.avoidlistItemsList
#First we do the easy stuff, all the global options
for element, old_data in globalOptions.iteritems():
live_element = eval("self.context." + str(element))
element_data = self.typeMatcher(live_element, "READ")()
if old_data != element_data:
is_changed = True
break
#Now the more complex bits, we check the watchlist and avoidlist here.
if is_changed is False:
if self.checkListChanges(avoidlist, alOptions) is True or self.checkListChanges(watchlist, wlOptions) is True:
is_changed = True
if is_changed is True:
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "SCCwatcher", "There are unsaved changes to the current file.", QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel)
#msgBox.setTitle("")
msgBox.setWindowIcon(self.context.icon)
msgBox.setInformativeText("Do you want to save these changes?")
msgBox.setStandardButtons(QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel)
msgBox.setDefaultButton(QtGui.QMessageBox.Save)
rtn = msgBox.exec_()
return [True, rtn]
else:
return [False, None]
def updateUiStateInfo(self):
#This function will loop through all the GUI options, saving the state of all objects.
#This will allow us to detect whether or not a user has changed anything in the program since the last save/load operation
#Was worried that somehow there would still be an active ref to the old state info so I decided to del() it first
del(self.context.SettingsManager.guiState["watchlistState"])
del(self.context.SettingsManager.guiState["avoidlistState"])
wlOptions = OD()
alOptions = OD()
self.context.SettingsManager.guiState["watchlistState"] = wlOptions
self.context.SettingsManager.guiState["avoidlistState"] = alOptions
watchlist = self.context.WLGwatchlistItemsList
avoidlist = self.context.avoidlistItemsList
globalOptions = self.context.SettingsManager.guiState["globalOptionsState"]
#Global options
for element, old_data in globalOptions.iteritems():
#First turn the element live so we can check it
live_element = eval("self.context." + str(element))
#Now get the elements current state
element_data = self.typeMatcher(live_element, "READ")()
globalOptions[element] = element_data
#Watchlist
for cur_index in xrange(0, watchlist.count()):
cur_WL_item = watchlist.item(cur_index)
if cur_WL_item == 0:
continue
cur_WL_title = str(cur_WL_item.text())
cur_WL_data = cur_WL_item.data(Qt.UserRole).toPyObject()
#Save it
wlOptions[cur_WL_title] = cur_WL_data
#Avoidlist
for cur_index in xrange(0, avoidlist.count()):
cur_AL_item = avoidlist.item(cur_index)
if cur_AL_item == 0:
continue
cur_AL_title = str(cur_AL_item.text())
cur_AL_data = cur_AL_item.data(Qt.UserRole).toPyObject()
#Save it
alOptions[cur_AL_title] = cur_AL_data
def removeWatchListItem(self):
#This function will remove the currently selected item in the watch list
self.removeListItem(self.context.WLGwatchlistItemsList)
def removeAvoidListItem(self):
#This function will remove the currently selected item in the avoid list
self.removeListItem(self.context.avoidlistItemsList)
#I really don't like repeating myself so I've made a third function here with all the actual code for removing items from watchlists.
def removeListItem(self, access_object):
#Get the currently selected item
current_selection = access_object.currentItem()
#Save it to undo/redo stack
self.context.undoRedoSystem.new_undoredo_QListWidget(access_object, current_selection, op="rem")
#Get the index of the current item
current_selection_index = access_object.row(current_selection)
#And now we remove our watch item from the QListWidget. This also removes any temporary data associated with this item at the same time.
removed_item = access_object.takeItem(current_selection_index)
del(removed_item) #Sometimes I don't trust the GC, and it can't hurt to be sure.
#Set focus to whatever item is below us if possible
if access_object.count() == current_selection_index:
access_object.setCurrentRow(current_selection_index-1)
else:
access_object.setCurrentRow(current_selection_index)
def addWatchListItem(self):
#This function will add a new list item to the watch list
#First thing we do is make a copy of the defaults for the watchlist, then populate it with values from the general options
#I am wondering now whether or not making a new deep copy each time we add a list item is a good idea or not. Might lead to a memory leak. Hopefully the GC is doin its job.
#Maybe we should instead create the structure once and then update the values as needed, although this has its own issues (like vestigial options, somehow lol, I dunno)
updated_wl_defaults = DC(self.context.SettingsManager.guiDefaults["watchlistDefaults"])
updated_wl_defaults["WLSGsavepathTextbox"] = self.context.ggSavepathTextbox.text()
updated_wl_defaults["WLSGexternalCommandTextbox"] = self.context.extCmdExeLocation.text()
updated_wl_defaults["WLSGexternalCommandArgsTextbox"] = self.context.extCmdExeArguments.text()
updated_wl_defaults["WLSGsizeLimitLowerTextbox"] = self.context.globalSizeLimitLowerTextbox.text()
updated_wl_defaults["WLSGsizeLimitLowerSuffixSelector"] = self.context.globalSizeLimitLowerSuffixSelector.currentIndex()
updated_wl_defaults["WLSGsizeLimitUpperTextbox"] = self.context.globalSizeLimitUpperTextbox.text()
updated_wl_defaults["WLSGsizeLimitUpperSuffixSelector"] = self.context.globalSizeLimitUpperSuffixSelector.currentIndex()
updated_wl_defaults["WLSGenableExternalCmdCheckbox"] = self.context.extCmdMasterEnableCheck.checkState()
updated_wl_defaults["WLSGdupecheckingCheckbox"] = self.context.globalDupecheckCheck.checkState()
updated_wl_defaults["WLSGutWebUiCheckox"] = self.context.utwuiMasterEnableTriCheck.checkState()
updated_wl_defaults["WLSGftpUploadCheckbox"] = self.context.ftpMasterEnableCheck.checkState()
updated_wl_defaults["WLSGemailCheckbox"] = self.context.emailMasterEnableCheck.checkState()
self.addNewListItem(self.context.WLGwatchlistItemsList, updated_wl_defaults)
def addAvoidListItem(self):
#This function will add a new list item to the avoid list
self.addNewListItem(self.context.avoidlistItemsList)
def addNewListItem(self, access_object, list_defaults=None):
#Temporarily disable sorting
__sortingEnabled = access_object.isSortingEnabled()
access_object.setSortingEnabled(False)
#Create our QListWidgetItem
item = QtGui.QListWidgetItem()
#Make sure our untitled entry isnt going to be a duplicate
item_title = self.checkForDuplicates(access_object, "Untitled Entry")
#Set its text
item.setText(_translate("sccw_SettingsUI", item_title, None))
#Set the default data items if we are a new watchlist item:
if list_defaults is not None:
#Set the title
list_defaults["WLSGwatchNameTextbox"] = item_title
else:
#Avoid, we just set the title, everythign else is default
list_defaults = {"avoidNameTextbox": item_title, "avoidFilterTextbox": "", "avoidFilterRegexCheck": 0}
item.setData(Qt.UserRole, list_defaults)
#And add the item to the list
access_object.addItem(item)
#We reenable sorting, if it was enabled before
access_object.setSortingEnabled(__sortingEnabled)
#Set focus to new object
access_object.setCurrentItem(item)
#Save the new item to undo/redo stack
self.context.undoRedoSystem.new_undoredo_QListWidget(access_object, item, op="add")
def checkForDuplicates(self, access_object, item_text, alt_match=None): #I hate adding params as needed
#This function will look for duplicate entries in the QWidgetList supplied as access_object
#If any duplicates are detected the item_text has a number appended to it (or has its appended number incremented) and is returned
#First we loop through each entry and see if its item_text matches anything in the watchlist.
for cur_index in xrange(0, access_object.count()):
cur_item = access_object.item(cur_index)
cur_item_text = cur_item.text()
if alt_match is not None:
#We make sure the current item isnt the one supplied by alt_match
if cur_item == alt_match: continue
#Check if the titles match
if cur_item_text == item_text:
#Ok we got a duplicate. Lets see if this dupe has a number appended or not
num_reg = re.match("^(.*?)\s\(([0-9]{1,3})\)$", cur_item_text)
if num_reg is not None:
#This dupe does have a number already. We'll get the number, increment it by one, and put it back.
num_end = int(num_reg.group(2)) + 1
new_title = "%s (%s)" % (num_reg.group(1), num_end)
else:
#Ok there is no number ending, now we just append a number.
new_title = "%s (1)" % (item_text)
#Finally, we recurse to be sure this new title isnt also a dupe.
final_return = self.checkForDuplicates(access_object, new_title)
return final_return
#No titles matched so we don't have a dupe. We return the correct item_text to confirm this.
return item_text
#Update functions for when anything is changed for a watchlist or avoidlist item.
#These two functions save all the data for the item, not just the piece of data that has changed.
def saveAllAvoidlistItems(self):
self.saveAllListItems(self.context.avoidlistItemsList, self.context.avoidNameTextbox, self.context.avoidListElements, self.context.SettingsManager.guiDefaults["avoidlistDefaults"])
def saveAllWatchlistItems(self):
self.saveAllListItems(self.context.WLGwatchlistItemsList, self.context.WLSGwatchNameTextbox, self.context.watchListElements, self.context.SettingsManager.guiDefaults["watchlistDefaults"])
def saveAllListItems(self, access_object, item_title_object, elements_list, defaults):
#Don't operate during a load operation
if self.__is_loading is True:
return
#Get the current list item
current_list_item = access_object.currentItem()
if current_list_item is None:
return
#First thing we do is make sure the title isnt a dupe.
#We run the title through a dupe checker and return a new title if necessary
cur_title = current_list_item.text() #I thought about using the item_title_object to get the title, but this is nicer.
cur_title = self.checkForDuplicates(access_object, cur_title, current_list_item)
#Now we set the returned title. If it wasnt a dupe it should be the same as before.
current_list_item.setText(cur_title)
#And update the textbox with the title in it
item_title_object.setText(cur_title)
#Now call the saveListData() function with the avoidlist elements and objects passed
self.saveListData(elements_list, current_list_item, defaults)
#These Three functions save the data associated with each watch or avoid item whenever the user switches watch items.
#The third is the master function while the other two just provide unique data tot he master.
def updateCurrentAvoidListSelection(self, new_listwidget_item, previous_listwidget_item):
self.updateCurrentListSelection(new_listwidget_item, previous_listwidget_item, self.context.avoidListElements, self.context.SettingsManager.guiDefaults["avoidlistDefaults"], self.context.avoidlistSettingsGroup)
def updateCurrentWatchListSelection(self, new_listwidget_item, previous_listwidget_item):
self.updateCurrentListSelection(new_listwidget_item, previous_listwidget_item, self.context.watchListElements, self.context.SettingsManager.guiDefaults["watchlistDefaults"], self.context.watchlistSettingsGroup)
def updateCurrentListSelection(self, new_listwidget_item, previous_listwidget_item, listwidget_elements, reset_data, opgrp_access_object):
#Set the load var so nothing crappy happens
self.__is_loading = True
#Save data
#If things go south here, its probably because its an Untitled entry or theres no entry at all.
#We still want to reset though so we allow it to pass even if it fails
if previous_listwidget_item is not None:
self.saveListData(listwidget_elements, previous_listwidget_item, reset_data)
#reset listwidget
self.clearUiData(reset_data)
#Finally, load new data if necessary
if new_listwidget_item is not None:
new_data = new_listwidget_item.data(Qt.UserRole).toPyObject()
if new_data is not None:
self.loadListData(new_data)
#Set the current selection to this item to be sure
new_listwidget_item.listWidget().setCurrentItem(new_listwidget_item)
#Now that we have loaded up our data, we enable the watch/avoid list's option groups if necessary
if opgrp_access_object.isEnabled() is False:
opgrp_access_object.setEnabled(True)
else:
#Ok so we know new_listwidget_item is NoneType, this means the user has no item selected.
#We will disable the group of options adjacent to the listwidget object
opgrp_access_object.setDisabled(True)
#Update sizecheck if on tab 2
if self.context.tabWidget.currentIndex() == 2:
self.checkSizeLimitBounds("wlist")
#And set the load var again to its normal state
self.__is_loading = False
#These three functions deal with saving, clearing, and loading from watchlists.
def saveListData(self, listwidget_elements, listwidget_item, defaults):
item_save_data = DC(defaults)
#Loop through each item in listwidget_elements
for element in listwidget_elements:
live_element = eval("self.context." + str(element))
if len(listwidget_elements[element]) == 3:
#Special case for size-limit selectors. We have to save the index of the dropdown list.
#Get data for both elements.
prefix, suffix = self.typeMatcher(live_element, "SLC_READ", listwidget_elements[element][2], sc=True)
#Save the data for the textbox
item_save_data[element] = prefix
#And store the data for the dropdown box
item_save_data[listwidget_elements[element][2]] = suffix
else:
#Get our access function to read the data from the live element into our save dict
item_save_data[element] = self.typeMatcher(live_element, "READ")()
#We may want to now get the write function to "zero out" the form. This may be better put in its own function however. (why?)
#Now we have an OrderedDict with our data to save in it, we store it inside the element using the setData() function.
#We will be saving the data in the Qt.UserRole role to the previous qlistwidgetitem we just had selected.
if hasattr(listwidget_item, "setData"): listwidget_item.setData(Qt.UserRole, item_save_data)
def clearUiData(self, reset_data):
for element, data in reset_data.iteritems():
live_element = eval("self.context." + str(element))
write_function, dtype = self.typeMatcher(live_element, "WRITE")
if dtype == "str": data = str(data)
if dtype == "int": data = int(data)
write_function(data)
def loadListData(self, new_data):
#Ok we do have data, so lets set the form up with this data
for element, data in new_data.iteritems():
live_element = eval("self.context." + str(element))
write_function, datatype = self.typeMatcher(live_element, "WRITE")
#Handle all checkboxes that aren't tristate. This just converts the 1's to 2's.
if "QCheckBox" in str(type(live_element)):
if element != "utwuiMasterEnableTriCheck" and element != "WLSGutWebUiCheckox":
if int(data) == 1:
data = 2
if datatype == "str":
data = str(data)
if datatype == "int":
try:
data = int(data)
except:
data = 0
#And now we update the element with the new data
write_function(data)
def updateCurrentWatchTitle(self, text):
current_item = self.context.WLGwatchlistItemsList.currentItem()
if current_item is not None:
current_item.setText(text)
def updateCurrentAvoidTitle(self, text):
current_item = self.context.avoidlistItemsList.currentItem()
if current_item is not None:
current_item.setText(text)
def clearList(self, access_object):
#This function will remove all items from the QListWidget supplied as access_object
while access_object.count() > 0:
removed_item = access_object.takeItem(0)
del(removed_item) #Sometimes I don't trust the GC, and it can't hurt to be sure.
def loadUiState(self, dd_filename=None):
#Takes in the data format of loadSettings() and updates the UI with the data received
#We will go through data{} and use the access method detailed in the uiElements dictionary.
#The two's structure are identical, making this task extremely simple.
#prompt the user if they want to save or not
#Check if we need to ask the user to save changes
has_changed, rtn = self.checkUiStateChange()
#If there were changes, check how the user wanted us to proceed
if has_changed is True:
if rtn == QtGui.QMessageBox.Save:
if self.saveUiToFile() is False:
return
elif rtn == QtGui.QMessageBox.Discard:
pass
elif rtn == QtGui.QMessageBox.Cancel:
return
#Now that we have either saved or discared any changes made, we prompt the user to open up a new file
#Get the new file name and tell the SettingsManager to update its QSettings object to use the new file location
#We bypass this part of the user drag-and-drop'd a file
if dd_filename is not None:
filename = dd_filename
else:
filename = self.browse_button_loadFile()
#Discontinue if the user canceled selection of a file
if len(filename) < 1:
return
#close the old file first
self.context.SettingsManager.closeSettingsFile()
#Clear UI state
self.clearGUIState()
#Load up the data
self.context.SettingsManager.openSettingsFile(filename)
loaded_data = self.context.SettingsManager.loadSettings()
#We have to convert the ini option names back into the element object's name.
converted_data = OD()
#Set the initial state of the data to the blank-slate defaults. This will catch any options the user forgot to include
converted_data["GlobalSettings"] = DC(self.context.SettingsManager.guiDefaults["allOtherDefaults"])
#converted_data["watch"] = DC(self.context.SettingsManager.guiDefaults["watchlistDefaults"])
#converted_data["avoid"] = DC(self.context.SettingsManager.guiDefaults["avoidlistDefaults"])
#Check if we have the correct settings, if not notify the user and discontinue current operation
if loaded_data.has_key("GlobalSettings") is False:
#The settings ini we are trying to load doesnt have our main settings section
#We will assume this is not a correct file and give an error to the user
text = "There was an error while trying to load the provided settings file. This file doesn't look like a valid SCCwatcher 2.0 settings file."
errorbox = QtGui.QMessageBox(QtGui.QMessageBox.Critical, "SCCwatcher", text, QtGui.QMessageBox.Ok)
errorbox.setWindowIcon(self.context.icon)
errorbox.exec_()
self.context.SettingsManager.closeSettingsFile()
#Set the title because by now we've already closed the old file so we are effectively at a New File state.
self.updateUiTitle("New Settings File")
return
#First we do the general options
converted_data["GlobalSettings"] = OD()
keys_to_match = self.context.SettingsManager.REVelementsToOptions.keys()
for key, value in loaded_data["GlobalSettings"].iteritems():
#While the below seems odd and roundaboutish for simple retrieving of data, I needed to be able to match a dict key against a string that
#might not be the same case. If I just .lower'd() everything it would make the configs look ugly without caps differentiating the words.
#At some point, no matter the solution, the lowercase keys must somehow be matched against the cased keys. We then need to return the cased key.
cased_key = ""
for i in keys_to_match:
match_string = re.search("(" + str(key) + ")", i, re.I)
if match_string is not None:
#Got our cased key
cased_key = match_string.group(1)
if len(cased_key) == 0:
print "SCCv2GUI.ERROR: Couldn't match cased_key against REVelementsToOptions. Erronious entry: %s" % (key,)
continue
objectname = self.context.SettingsManager.REVelementsToOptions[cased_key]
converted_data["GlobalSettings"][str(objectname)] = value
#Clean up loaded_data so we are left with just watches and avoids.
del(loaded_data["GlobalSettings"])
tmpwatch = OD()
tmpavoid = OD()
converted_data["watch"] = OD()
converted_data["avoid"] = OD()
#Sort the watches and avoids as well as fix the casing on options, like the global options above
#Again, this isn't elegant but I'm done with elegant. Now I just want this working and done.
for key, val in loaded_data.iteritems():
if key[0] == "-":
#Avoid item
tmpavoid[key] = val
else:
#Watch item
tmpwatch[key] = val
#Watchlist first
for entry in tmpwatch:
converted_data["watch"][entry] = OD()
for key, value in tmpwatch[entry].iteritems():
cased_key = ""
for i in self.context.SettingsManager.REVwatchListElements.keys():
match_string = re.search("(" + str(key) + ")", i, re.I)
if match_string is not None:
cased_key = match_string.group(1)
if len(cased_key) == 0:
print "SCCv2GUI.ERROR: Couldn't match cased_key against REVwatchListElements. Erronious entry: %s" % (key,)
continue
converted_data["watch"][entry][cased_key] = value
#Avoidlist second. And yes I know this should probably be its own function but its being used twice and I'm super braindead right now
#If it works it works, JUST DO IT
for entry in tmpavoid:
converted_data["avoid"][entry] = OD()
for key, value in tmpavoid[entry].iteritems():
cased_key = ""
for i in self.context.SettingsManager.REVavoidListElements.keys():
match_string = re.search("(" + str(key) + ")", i, re.I)
if match_string is not None:
cased_key = match_string.group(1)
if len(cased_key) == 0:
print "SCCv2GUI.ERROR: Couldn't match cased_key against REVavoidListElements. Erronious entry: %s" % (key,)
continue
objectname = self.context.SettingsManager.REVavoidListElements[cased_key]
converted_data["avoid"][entry][cased_key] = value
#Remove temp stuffs
del(tmpavoid)
del(tmpwatch)
#Ok now converted_data has three subdicts called: GlobalSettings, watch, and avoid.
#We now go about the business of setting the GUI up with the loaded data.
#First we do the GlobalSettings.
#We look through our elementsToOptions dict and set each options as we come upon it.
for element, einfos in self.context.SettingsManager.elementsToOptions.iteritems():
#If we come upon an option in our element list that doesn't exist in the ini file, we just skip it.
#The defaults in the GUI will take over from there.
if converted_data["GlobalSettings"].has_key(element) is False:
continue
data = converted_data["GlobalSettings"][element]
#Check if there is any data. If there isn't, then the defaults will kick in again
if len(data) == 0:
continue
#Make a live access object from element and then use its type to get our access function
access_string = "self.context." + str(element)
live_element_obj = eval(access_string)
#we use typeMatcher() to return our write function
access_function, datatype = self.typeMatcher(live_element_obj, "WRITE")
#Handle all checkboxes that aren't tristate. This just converts the 1's to 2's.
if "QCheckBox" in str(type(live_element_obj)):
if element != "utwuiMasterEnableTriCheck" and element != "WLSGutWebUiCheckox":
if int(data) == 1:
data = 2
#special case for size limit selector
if len(einfos) > 2:
prefix = ""
suffix = ""
#Check if we have any data, if not we just move along
if len(data) < 1:
continue
#Split up the data into two part, prefix and suffix
try:
prefix, suffix = re.match("([0-9]{1,9})(?:\s+)?([A-Za-z]{2})?", data).groups()
if len(suffix) > 0:
suffix = self.convertIndex(suffix)
except:
#Probably set weird
pass