-
Notifications
You must be signed in to change notification settings - Fork 1
/
manager.py
1285 lines (1094 loc) · 58.6 KB
/
manager.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
import csv
import pygtk
pygtk.require('2.0')
import gtk
import shutil
import os
import apsw
import types
import gobject
class Workspace:
''' Each Workspace has its own persistent database file. '''
db0 = os.path.join(os.getcwd(), 'workspace.sqlite')
def __init__(self, name='Workspace', db_file=db0):
self.name = name
self.disk_db = db_file
self.projects = []
self.memory = self.connect(":memory:")
def connect(self, db):
''' Connect to the DB, enable foreign keys, set autocommit mode,
and return the open connection object. '''
con = apsw.Connection(db)
cur = con.cursor()
cur.execute('PRAGMA foreign_keys = ON')
cur.close()
return con
def list_projects(self):
''' Returns a list of BOM project tables in the DB. '''
projects = []
try:
cur = self.memory.cursor()
cur.execute('SELECT name FROM projects ORDER BY name')
for row in cur.fetchall():
projects.append(row[0])
finally:
cur.close()
return projects
def open(self):
''' Loads the workspace into memory from disk. '''
bkup = self.memory.backup("main", self.connect(self.disk_db), "main")
try:
while not bkup.done:
bkup.step()
#print bkup.remaining, bkup.pagecount, "\r",
finally:
bkup.finish()
def save(self):
''' Saves the in-memory workspace to disk. '''
with self.connect(self.disk_db).backup("main", self.memory, "main") as bkup:
while not bkup.done:
bkup.step(100)
def create_tables(self):
''' Create the workspace-wide database tables. '''
try:
cur = self.memory.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS projects(name TEXT PRIMARY KEY, description TEXT, infile TEXT)')
cur.execute('''CREATE TABLE IF NOT EXISTS parts
(name TEXT NOT NULL,
project TEXT NOT NULL REFERENCES projects(name) ON DELETE CASCADE ON UPDATE CASCADE,
value TEXT,
device TEXT,
package TEXT,
description TEXT,
product TEXT REFERENCES products(manufacturer_pn) ON DELETE SET NULL ON UPDATE CASCADE,
PRIMARY KEY(name, project))''')
cur.execute('''CREATE TABLE IF NOT EXISTS part_attributes
(id INTEGER PRIMARY KEY,
part TEXT NOT NULL,
project TEXT NOT NULL,
name TEXT NOT NULL,
value TEXT NOT NULL,
FOREIGN KEY(part, project) REFERENCES parts(name, project) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE(part ASC, project ASC, name) ON CONFLICT REPLACE)''')
cur.execute('''CREATE TABLE IF NOT EXISTS products
(manufacturer TEXT,
manufacturer_pn TEXT PRIMARY KEY,
datasheet TEXT,
description TEXT,
package TEXT,
UNIQUE (manufacturer ASC, manufacturer_pn ASC) ON CONFLICT FAIL)''')
cur.execute("INSERT OR REPLACE INTO products VALUES ('NULL','NULL','NULL','NULL','NULL')")
cur.execute('''CREATE TABLE IF NOT EXISTS listings
(vendor TEXT,
vendor_pn TEXT NOT NULL PRIMARY KEY,
manufacturer_pn TEXT REFERENCES products(manufacturer_pn) ON DELETE CASCADE ON UPDATE CASCADE,
inventory INTEGER,
packaging TEXT,
reelfee FLOAT,
category TEXT,
family TEXT,
series TEXT)''')
cur.execute('''CREATE TABLE IF NOT EXISTS pricebreaks
(id INTEGER PRIMARY KEY,
pn TEXT REFERENCES listings(vendor_pn) ON DELETE CASCADE ON UPDATE CASCADE,
qty INTEGER,
unit DOUBLE,
UNIQUE(pn ASC, qty ASC, unit) ON CONFLICT REPLACE)''')
cur.execute('''CREATE TABLE IF NOT EXISTS preferred_listings
(id INTEGER PRIMARY KEY,
project TEXT REFERENCES projects(name) ON DELETE CASCADE ON UPDATE CASCADE,
product TEXT REFERENCES products(manufacturer_pn) ON DELETE CASCADE ON UPDATE CASCADE,
listing TEXT REFERENCES listings(vendor_pn) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE(project ASC, product) ON CONFLICT REPLACE)''')
finally:
cur.close()
wspace = Workspace()
#active_project_name = 'test1'
input_file = os.path.join(os.getcwd(), "test.csv") # TODO: Test dummy
#active_bom = BOM("test1", 'Test BOM 1', wspace, os.path.join(os.getcwd(), "test.csv"))
def set_combo(combo_box, iter_text):
'''Sets the active index of a gtk.ComboBox to the index with given string.
iter_text must be an exact match to the string in combo_box.get_model().
Returns True if the desired row was found and activated.'''
model = combo_box.get_model()
iter = model.get_iter_root()
success = False
while iter is not None and success is False:
model_text = model.get_value(iter, 0)
if iter_text == model_text:
success = True
combo_box.set_active_iter(iter)
else:
iter = model.iter_next(iter)
return success
class Manager(gobject.GObject):
'''Main GUI class'''
def delete_event(self, widget, event, data=None):
print "delete event occurred"
return False
def destroy(self, widget, data=None):
gtk.main_quit()
# -------- CALLBACK METHODS --------
def file_save_callback(self, widget):
''' Callback for the Save button in the File menu. '''
wspace.save()
def new_project_input_file_callback(self, widget, data=None):
''' Callback for the input file Open dialog in the New Project dialog. '''
self.input_file_dialog.run()
self.input_file_dialog.hide()
if self.input_file_dialog.get_filename() is None:
self.new_project_input_file_entry.set_text('')
else:
self.new_project_input_file_entry.set_text(self.input_file_dialog.get_filename())
def project_new_callback(self, widget, data=None):
'''Callback for the New Project button. '''
response = self.new_project_dialog.run()
self.new_project_dialog.hide()
new_name = self.new_project_name_entry.get_text()
curProjects = wspace.list_projects(wspace.memory)
if new_name in curProjects:
print 'Error: Name in use!'
self.project_name_taken_dialog.run()
self.project_name_taken_dialog.hide()
elif response == gtk.RESPONSE_ACCEPT:
# Create project
print 'Creating new project'
new = BOM.new_project(new_name, self.new_project_description_entry.get_text(), self.new_project_input_file_entry.get_text(), wspace.memory)
self.project_store_populate()
self.new_project_name_entry.set_text('')
self.new_project_description_entry.set_text('')
#self.new_project_workspace_entry.set_text('')
self.new_project_input_file_entry.set_text('')
def project_open_callback(self, widget, data=None):
''' Callback for the Open Project button. '''
(model, row_iter) = self.project_tree_view.get_selection().get_selected()
self.active_bom = BOM.read_from_db(model.get(row_iter,0)[0], wspace.memory)[0]
self.active_project_name = model.get(row_iter,0)[0]
self.active_bom.parts = self.active_bom.read_parts_list_from_db(wspace.memory)
input_file = model.get(row_iter,3)[0]
#print self.active_bom, type(self.active_bom)
#print 'Project name: ', self.active_project_name
#print 'Project CSV: ', input_file
if self.bom_group_name.get_active():
self.bom_store_populate_by_name()
elif self.bom_group_value.get_active():
self.bom_store_populate_by_value()
elif self.bom_group_product.get_active():
self.bom_store_populate_by_product()
self.bom_tree_view.columns_autosize()
# TODO: Uncomment this when spin is working
#self.order_size_spin_callback(self.run_size_spin)
self.window.show_all()
def project_delete_callback(self, widget, data=None):
''' Callback for the Delete Project button. '''
(model, row_iter) = self.project_tree_view.get_selection().get_selected()
selected_bom = BOM.read_from_db(model.get(row_iter,0)[0], wspace.memory)[0]
if selected_bom.name == self.active_bom.name:
self.bom_store.clear()
selected_bom.delete(wspace.memory)
self.project_store_populate(wspace.memory)
'''Callback for the "Read CSV" button on the BOM tab.'''
def read_input_callback(self, widget, data=None):
self.active_bom.read_from_file(wspace.memory)
if self.bom_group_name.get_active():
self.bom_store_populate_by_name()
elif self.bom_group_value.get_active():
self.bom_store_populate_by_value()
elif self.bom_group_product.get_active():
self.bom_store_populate_by_product()
self.window.show_all()
'''Callback for the "Read DB" button on the BOM tab.'''
def bom_read_db_callback(self, widget, data=None):
#print "BOM Read DB callback"
#print 'Parts list = ', self.active_bom.parts
if self.bom_group_name.get_active():
self.bom_store_populate_by_name()
elif self.bom_group_value.get_active():
self.bom_store_populate_by_value()
elif self.bom_group_product.get_active():
self.bom_store_populate_by_product()
self.window.show_all()
'''Callback method triggered when a BOM line item is selected.'''
def bom_selection_callback(self, widget, data=None):
# Set class fields for currently selected item
(model, row_iter) = self.bom_tree_view.get_selection().get_selected()
#print 'row_iter is: ', row_iter, '\n'
#print 'model.get(row_iter,0)[0] is: ', model.get(row_iter,0)[0]
selected_name = model.get(row_iter,0)[0]
selected_pn = model.get(row_iter,5)[0]
if selected_name is not None and len(selected_name) != 0 and selected_name != '':
self.selected_bom_part = self.active_bom.select_parts_by_name(selected_name, wspace.memory)[0]
if selected_pn is not None and selected_pn != 'None' and selected_pn != '': # Look up part in DB
self.selected_product = Product.select_by_pn(selected_pn, wspace.memory)[0]
print 'selected_product: ', self.selected_product
self.part_info_datasheet_button.set_sensitive(True)
self.part_info_scrape_button.set_sensitive(True)
# Set class field for currently selected product
if len(self.selected_product.listings) == 0:
self.selected_product.scrape(wspace.memory)
self.set_part_info_labels(self.selected_product)
self.populate_part_info_listing_combo(self.selected_product)
self.destroy_part_price_labels()
if type(self.part_info_listing_combo.get_active_text()) is not types.NoneType and self.part_info_listing_combo.get_active_text() != '':
#Set the active Listing selection if one has been set
assert len(self.selected_product.listings.keys()) > 0
preferred_listing = self.selected_product.get_preferred_listing(self.active_bom, wspace.memory)
if preferred_listing is not None:
set_combo(self.part_info_listing_combo, preferred_listing.key())
self.set_part_price_labels(self.selected_product.listings[self.part_info_listing_combo.get_active_text()])
else:
self.part_info_datasheet_button.set_sensitive(False)
self.part_info_scrape_button.set_sensitive(False)
self.populate_part_info_listing_combo()
self.destroy_part_price_labels()
self.clear_part_info_labels()
'''Callback method activated by the BOM grouping radio buttons.
Redraws the BOM TreeView with the approporiate goruping for the selected radio.'''
def bom_group_callback(self, widget, data=None):
#print "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
# Figure out which button is now selected
if widget.get_active():
if 'name' in data:
self.bom_store_populate_by_name()
elif 'value' in data:
self.bom_store_populate_by_value()
elif 'product' in data:
self.bom_store_populate_by_product()
self.window.show_all()
'''Callback method activated by clicking a BOM column header.
Sorts the BOM TreeView by the values in the clicked column.'''
def bom_sort_callback(self, widget):
print widget.get_sort_order()
widget.set_sort_column_id(0)
# TODO: On sorting by a different column, the indicator does not go away
# This may be because with the current test set, the other columns are still technically sorted
'''Callback method for the "Edit Part" button in the BOM tab.
Opens a dialog window with form fields for each BOM Part object field.'''
def bom_edit_part_callback(self, widget, data=None):
# Open a text input prompt window
edit_part_dialog = gtk.Dialog("Edit part", self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
# -------- DECLARATIONS --------
# Field labels
edit_part_name_label = gtk.Label("Name: ")
edit_part_value_label = gtk.Label("Value: ")
edit_part_device_label = gtk.Label("Device: ")
edit_part_package_label = gtk.Label("Package: ")
edit_part_description_label = gtk.Label("Description: ")
edit_part_manufacturer_label = gtk.Label("Manufacturer: ")
edit_part_manufacturer_pn_label = gtk.Label("Manufacturer Part Number: ")
# Field entry elements
self.edit_part_name_entry = gtk.Entry()
self.edit_part_value_entry = gtk.Entry()
self.edit_part_device_entry = gtk.Entry()
self.edit_part_package_entry = gtk.Entry()
self.edit_part_description_entry = gtk.Entry()
self.edit_part_product_entry = gtk.Entry()
# Return values
self.product_entry_text = ""
# HBoxes
edit_part_dialog_name_hbox = gtk.HBox()
edit_part_dialog_value_hbox = gtk.HBox()
edit_part_dialog_device_hbox = gtk.HBox()
edit_part_dialog_package_hbox = gtk.HBox()
edit_part_dialog_description_hbox = gtk.HBox()
edit_part_dialog_manufacturer_hbox = gtk.HBox()
edit_part_dialog_manufacturer_pn_hbox = gtk.HBox()
# -------- CONFIGURATION --------
# Label alignment
edit_part_name_label.set_alignment(0.0, 0.5)
edit_part_value_label.set_alignment(0.0, 0.5)
edit_part_device_label.set_alignment(0.0, 0.5)
edit_part_package_label.set_alignment(0.0, 0.5)
edit_part_description_label.set_alignment(0.0, 0.5)
edit_part_manufacturer_label.set_alignment(0.0, 0.5)
edit_part_manufacturer_pn_label.set_alignment(0.0, 0.5)
# Set default text of entry fields to current part values
self.edit_part_name_entry.set_text(self.selected_bom_part.name)
self.edit_part_value_entry.set_text(self.selected_bom_part.value)
self.edit_part_device_entry.set_text(self.selected_bom_part.device)
self.edit_part_package_entry.set_text(self.selected_bom_part.package)
self.edit_part_description_entry.set_text(self.selected_bom_part.description)
if self.selected_bom_part.product is None:
self.edit_part_product_entry.set_text('')
else:
self.edit_part_product_entry.set_text(self.selected_bom_part.product.manufacturer_pn)
# Pack labels/entry fields into HBoxes
edit_part_dialog_name_hbox.pack_start(edit_part_name_label, False, True, 0)
edit_part_dialog_name_hbox.pack_end(self.edit_part_name_entry, False, True, 0)
edit_part_dialog_value_hbox.pack_start(edit_part_value_label, False, True, 0)
edit_part_dialog_value_hbox.pack_end(self.edit_part_value_entry, False, True, 0)
edit_part_dialog_device_hbox.pack_start(edit_part_device_label, False, True, 0)
edit_part_dialog_device_hbox.pack_end(self.edit_part_device_entry, False, True, 0)
edit_part_dialog_package_hbox.pack_start(edit_part_package_label, False, True, 0)
edit_part_dialog_package_hbox.pack_end(self.edit_part_package_entry, False, True, 0)
edit_part_dialog_description_hbox.pack_start(edit_part_description_label, False, True, 0)
edit_part_dialog_description_hbox.pack_end(self.edit_part_description_entry, False, True, 0)
edit_part_dialog_manufacturer_pn_hbox.pack_start(edit_part_manufacturer_pn_label, True, True, 0)
edit_part_dialog_manufacturer_pn_hbox.pack_end(self.edit_part_product_entry, gtk.RESPONSE_ACCEPT)
# Pack HBoxes into vbox
edit_part_dialog.vbox.set_spacing(1)
edit_part_dialog.vbox.pack_start(edit_part_dialog_name_hbox, True, True, 0)
edit_part_dialog.vbox.pack_start(edit_part_dialog_value_hbox, True, True, 0)
edit_part_dialog.vbox.pack_start(edit_part_dialog_device_hbox, True, True, 0)
edit_part_dialog.vbox.pack_start(edit_part_dialog_package_hbox, True, True, 0)
edit_part_dialog.vbox.pack_start(edit_part_dialog_description_hbox, True, True, 0)
edit_part_dialog.vbox.pack_start(edit_part_dialog_manufacturer_pn_hbox, True, True, 0)
# Show everything
edit_part_dialog.vbox.show_all()
response = edit_part_dialog.run()
edit_part_dialog.hide()
if response == gtk.RESPONSE_ACCEPT:
# If the product text entry field is left blank, set the product to 'NULL'
if type(self.edit_part_product_entry.get_text()) is types.NoneType or len(self.edit_part_product_entry.get_text()) == 0:
self.product_entry_text = ''
self.selected_bom_part.product = None
else:
self.product_entry_text = self.edit_part_product_entry.get_text()
if self.selected_bom_part.product is not None and self.selected_bom_part.product.manufacturer_pn == self.product_entry_text:
pass # Nothing to do here
else:
prod = Product.select_by_pn(self.product_entry_text, wspace.memory)
if len(prod) > 0:
self.selected_bom_part.product = prod[0]
else:
newprod = Product('NULL', self.product_entry_text)
newprod.insert(wspace.memory)
newprod.scrape(wspace.memory)
self.selected_bom_part.product = newprod
if len(self.selected_bom_part.product.listings) == 0:
self.selected_bom_part.product.scrape(wspace.memory)
self.selected_product = self.selected_bom_part.product
# Set selected_bom_part
self.selected_bom_part.name = self.edit_part_name_entry.get_text()
self.selected_bom_part.value = self.edit_part_value_entry.get_text()
self.selected_bom_part.device = self.edit_part_device_entry.get_text()
self.selected_bom_part.package = self.edit_part_package_entry.get_text()
self.selected_bom_part.description = self.edit_part_description_entry.get_text()
# We need to check the products table for this Product, creating an entry
# for it if necessary, before updating selected_bom_part in the DB.
self.selected_bom_part.update(wspace.memory)
self.active_bom.update_parts_list(self.selected_bom_part)
if self.bom_group_name.get_active():
self.bom_store_populate_by_name()
elif self.bom_group_value.get_active():
self.bom_store_populate_by_value()
elif self.bom_group_product.get_active():
self.bom_store_populate_by_product()
self.populate_part_info_listing_combo(self.selected_bom_part.product)
if self.selected_bom_part.product is None or self.selected_bom_part.product.manufacturer_pn == 'NULL' or self.selected_bom_part.product.manufacturer_pn == '':
self.clear_part_info_labels()
else:
self.set_part_info_labels(self.selected_bom_part.product)
self.part_info_datasheet_button.set_sensitive(True)
self.part_info_scrape_button.set_sensitive(True)
def bom_find_prod_callback(self, widget, data=None):
''' Calls bom.product_updater on selected part. '''
self.selected_bom_part.product_updater(wspace.memory)
if self.bom_group_name.get_active():
self.bom_store_populate_by_name()
elif self.bom_group_value.get_active():
self.bom_store_populate_by_value()
elif self.bom_group_product.get_active():
self.bom_store_populate_by_product()
def part_info_scrape_button_callback(self, widget):
''' Part info frame "Scrape" button callback. '''
self.selected_product.scrape(wspace.memory)
self.set_part_info_labels(self.selected_product)
self.populate_part_info_listing_combo(self.selected_product)
if type(self.part_info_listing_combo.get_active_text()) is not types.NoneType and self.part_info_listing_combo.get_active_text() != '':
self.set_part_price_labels(self.selected_product.listings[self.part_info_listing_combo.get_active_text()])
self.part_info_inventory_content_label.set_text(str(self.selected_product.listings[self.part_info_listing_combo.get_active_text()].inventory))
self.part_info_set_listing_button.set_sensitive(True)
else:
self.part_info_set_listing_button.set_sensitive(False)
self.window.show_all()
def part_info_listing_combo_callback(self, widget, data=None):
self.destroy_part_price_labels()
if type(self.part_info_listing_combo.get_active_text()) is not types.NoneType and self.part_info_listing_combo.get_active_text() != '':
self.set_part_price_labels(self.selected_product.listings[self.part_info_listing_combo.get_active_text()])
self.part_info_inventory_content_label.set_text(str(self.selected_product.listings[self.part_info_listing_combo.get_active_text()].inventory))
self.part_info_set_listing_button.set_sensitive(True)
else:
self.part_info_set_listing_button.set_sensitive(False)
def part_info_set_listing_button_callback(self, widget, data=None):
print 'Set preferred listing callback'
if type(self.part_info_listing_combo.get_active_text()) is not types.NoneType and self.part_info_listing_combo.get_active_text() != '':
self.selected_product.set_preferred_listing(self.active_bom, self.selected_product.listings[self.part_info_listing_combo.get_active_text()], wspace.memory)
def order_size_spin_callback(self, widget):
''' Update the per-unit and total order prices when the order size
spin button is changed. '''
qty = self.run_size_spin.get_value_as_int()
(unit_price, total_cost) = self.active_bom.get_cost(wspace.memory, qty)
self.run_unit_price_content_label.set_text('$'+str(unit_price))
self.run_total_cost_content_label.set_text('$'+str(total_cost))
def db_store_populate(self):
''' Clear self.db_product_store and repopulate it. '''
self.db_product_store.clear()
prods = Product.select_all(wspace.memory)
for p in prods:
iter = self.db_product_store.append(None, [p.manufacturer, p.manufacturer_pn, p.description, p.datasheet, p.package])
self.db_tree_view.columns_autosize()
def db_read_database_callback(self, widget, data=None):
'''Callback for the "Read DB" button on the product DB tab.'''
print "Read DB callback"
self.db_store_populate()
def db_selection_callback(self, widget, data=None):
'''Callback method triggered when a product DB item is selected.'''
# Set class fields for currently selected item
(model, row_iter) = self.db_tree_view.get_selection().get_selected()
self.db_selected_product = Product.select_by_pn(model.get(row_iter,1)[0], wspace.memory)[0]
def db_sort_callback(self, widget):
'''Callback method activated by clicking a DB column header.
Sorts the DB TreeView by the values in the clicked column.'''
widget.set_sort_column_id(0)
# -------- HELPER METHODS --------
def project_store_populate(self):
self.project_store.clear()
# Columns: Name, Description, Database, Input File
projects_list = wspace.list_projects()
#print 'projects_list: ', projects_list
for p in projects_list:
if type(p) is types.NoneType:
print 'NoneType caught in projects_list'
elif p != 'dummy':
#print 'p = ', p
bom = BOM.read_from_db(p, wspace.memory)[0]
#print 'Returned BOM: ', bom, type(bom)
iter = self.project_store.append([bom.name, bom.description, wspace.name, bom.input])
self.project_tree_view.columns_autosize()
def bom_store_populate_by_name(self):
''' Clear self.bom_store and repopulate it, grouped by name. '''
self.bom_store.clear()
for p in self.active_bom.parts:
try:
part = self.active_bom.select_parts_by_name(p[0], wspace.memory)[0]
except IndexError:
print 'IndexError:'
print 'bom.parts: ', self.active_bom.parts
print 'p: ', p
print 'p[0]:', p[0]
#print 'Trying Part.select_all():', Part.select_all(wspace.memory)
if part.product is None:
iter = self.bom_store.append(None, [part.name, part.value, part.device, part.package, part.description, '', ''])
else:
iter = self.bom_store.append(None, [part.name, part.value, part.device, part.package, part.description, part.product.manufacturer_pn, ''])
self.bom_tree_view.columns_autosize()
def bom_store_populate_by_value(self):
''' Clear self.bom_store and repopulate it, grouped by value. '''
self.bom_store.clear()
self.active_bom.sort_by_val()
self.active_bom.set_val_counts(wspace.memory)
for val in self.active_bom.val_counts.keys():
# Make a row in the model for this value
if val == '':
group_iter = self.bom_store.append(None, ['', 'None', '', '', '', '', str(self.active_bom.val_counts[val])])
else:
group_iter = self.bom_store.append(None, ['', val, '', '', '', '', str(self.active_bom.val_counts[val])])
group = self.active_bom.select_parts_by_value(val, wspace.memory)
for part in group:
if part.product is None:
iter = self.bom_store.append(group_iter, [part.name, part.value, part.device, part.package, part.description, '', ''])
else:
iter = self.bom_store.append(group_iter, [part.name, part.value, part.device, part.package, part.description, part.product.manufacturer_pn, ''])
self.bom_tree_view.columns_autosize()
def bom_store_populate_by_product(self):
''' Clear self.bom_store and repopulate it, grouped by part number. '''
self.bom_store.clear()
self.active_bom.sort_by_prod()
self.active_bom.set_prod_counts(wspace.memory)
for prod in self.active_bom.prod_counts.keys():
# Make a row in the model for this product
if prod is None or prod == '' or len(prod) == 0 or prod == 'NULL':
group_iter = self.bom_store.append(None, ['', '', '', '', '', 'None', str(self.active_bom.prod_counts[prod])])
else:
group_iter = self.bom_store.append(None, ['', '', '', '', '', prod, str(self.active_bom.prod_counts[prod])])
group = self.active_bom.select_parts_by_product(prod, wspace.memory)
for part in group:
if part.product is None:
iter = self.bom_store.append(group_iter, [part.name, part.value, part.device, part.package, part.description, '', ''])
else:
iter = self.bom_store.append(group_iter, [part.name, part.value, part.device, part.package, part.description, part.product.manufacturer_pn, ''])
self.bom_tree_view.columns_autosize()
def set_part_info_labels(self, prod):
'''Set the Part Information pane fields based on the fields of a given
product object.'''
self.part_info_manufacturer_content_label.set_text(prod.manufacturer)
self.part_info_manufacturer_pn_content_label.set_text(prod.manufacturer_pn)
self.part_info_description_content_label.set_text(prod.description)
self.part_info_datasheet_content_label.set_text(prod.datasheet)
self.part_info_package_content_label.set_text(prod.package)
def clear_part_info_labels(self):
'''Clears the Part Information pane fields, setting the text of each Label
object to a tab character.'''
self.part_info_manufacturer_content_label.set_text("\t")
self.part_info_manufacturer_pn_content_label.set_text("\t")
self.part_info_description_content_label.set_text("\t")
self.part_info_datasheet_content_label.set_text("\t")
self.part_info_package_content_label.set_text("\t")
self.part_info_inventory_content_label.set_text("\t")
def set_part_info_listing_combo_to_preferred(self, prod):
'''Sets the active selection of self.part_info_listing_combo to a
preferred listing for the active project (if one exists). '''
preferred_listing = None
if type(prod) is not types.NoneType and prod.manufacturer_pn != 'NULL':
preferred_listing = prod.get_preferred_listing(self.active_bom, wspace.memory)
if preferred_listing is not None:
set_combo(self.part_info_listing_combo, preferred_listing.key())
else:
self.part_info_listing_combo.set_active(0)
def populate_part_info_listing_combo(self, prod=None):
''' Populates self.part_info_listing_combo with listings
for the selected Product. '''
#print 'Setting Listing combo...'
self.part_info_listing_combo.get_model().clear()
if type(prod) is not types.NoneType and prod.manufacturer_pn != 'NULL':
for listing in prod.listings.keys():
#print 'Listing: ', type(listing), listing
title = listing
#print 'Appending combo title: ', title
self.part_info_listing_combo.append_text(title)
self.set_part_info_listing_combo_to_preferred(prod)
self.part_info_vbox.show_all()
def destroy_part_price_labels(self):
for r in self.price_break_labels:
r.destroy()
for r in self.unit_price_labels:
r.destroy()
for r in self.ext_price_labels:
r.destroy()
del self.price_break_labels[:]
del self.unit_price_labels[:]
del self.ext_price_labels[:]
def set_part_price_labels(self, listing):
''' Given a listing, sets the pricing table labels. '''
n = len(listing.prices)
#print "n =", n
price_keys = sorted(listing.prices.keys())
#print "listing.prices = \n", listing.prices
#print "sorted(listing.prices.keys()) = \n", price_keys
self.part_info_pricing_table.resize(n+1, 3)
self.destroy_part_price_labels()
self.price_break_labels.append(gtk.Label("Price Break"))
self.unit_price_labels.append(gtk.Label("Unit Price"))
self.ext_price_labels.append(gtk.Label("Extended Price"))
self.part_info_pricing_table.attach(self.price_break_labels[0], 0, 1, 0, 1)
self.part_info_pricing_table.attach(self.unit_price_labels[0], 1, 2, 0, 1)
self.part_info_pricing_table.attach(self.ext_price_labels[0], 2, 3, 0, 1)
row_num = 1
for i in range(n):
#price_keys[i] is a key of listing.prices()
self.price_break_labels.append(gtk.Label(str(price_keys[i]) + ' '))
self.price_break_labels[row_num].set_alignment(0.5, 0.5)
self.unit_price_labels.append(gtk.Label(str(listing.prices[price_keys[i]]) + ' '))
self.unit_price_labels[row_num].set_alignment(1.0, 0.5)
self.ext_price_labels.append(gtk.Label(str( price_keys[i] * listing.prices[price_keys[i]]) + ' '))
self.ext_price_labels[row_num].set_alignment(1.0, 0.5)
self.part_info_pricing_table.attach(self.price_break_labels[row_num], 0, 1, row_num, row_num+1)
self.part_info_pricing_table.attach(self.unit_price_labels[row_num], 1, 2, row_num, row_num+1)
self.part_info_pricing_table.attach(self.ext_price_labels[row_num], 2, 3, row_num, row_num+1)
row_num += 1
self.part_info_frame.show_all()
def __init__(self):
# -------- DECLARATIONS --------
self.active_bom = BOM('dummy', 'Active BOM Declaration', input_file)
self.active_project_name = 'dummy'
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.main_box = gtk.VBox(False, 0)
self.menu_bar = gtk.MenuBar()
self.file_item = gtk.MenuItem("File")
self.file_menu = gtk.Menu()
self.new_item = gtk.MenuItem("New")
self.open_item = gtk.MenuItem("Open")
self.save_item = gtk.MenuItem("Save")
self.quit_item = gtk.MenuItem("Quit")
self.notebook = gtk.Notebook()
self.project_tab_label = gtk.Label("Projects")
self.bom_tab_label = gtk.Label("BOM Editor")
self.db_tab_label = gtk.Label("Product Database")
# --- Projects tab ---
self.project_box = gtk.VBox(False, 0) # First tab in notebook
self.project_toolbar = gtk.Toolbar()
self.project_new_button = gtk.ToolButton(None, "New Project")
self.project_open_button = gtk.ToolButton(None, "Open Project")
self.project_edit_button = gtk.ToolButton(None, "Edit Project")
self.project_delete_button = gtk.ToolButton(None, "Delete Project")
self.project_frame = gtk.Frame("Projects")
self.project_scroll_win = gtk.ScrolledWindow()
# New Project menu
self.new_project_dialog = gtk.Dialog('New Project', self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
self.new_project_name_hbox = gtk.HBox()
self.new_project_description_hbox = gtk.HBox()
#self.new_project_workspace_hbox = gtk.HBox()
self.new_project_input_file_hbox = gtk.HBox()
self.new_project_name_label = gtk.Label("Name: ")
self.new_project_description_label = gtk.Label("Description: ")
#self.new_project_workspace_label = gtk.Label("Workspace: ")
self.new_project_input_file_label = gtk.Label("Input file: ")
self.new_project_name_entry = gtk.Entry()
self.new_project_description_entry = gtk.Entry()
# TODO: Add a database file Entry/FileDialog when adding multiple DB file support
self.new_project_input_file_button = gtk.Button('Browse', gtk.STOCK_OPEN)
self.new_project_input_file_entry = gtk.Entry()
self.input_file_dialog = gtk.FileChooserDialog('Select Input File',
self.new_project_dialog, gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
self.project_name_taken_dialog = gtk.MessageDialog(self.new_project_dialog,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK, 'Error: Project name in use. \nPlease select a different name.')
# Projects list
# Columns: Name, Description, Workspace, Input File
self.project_store = gtk.ListStore(str, str, str, str)
self.project_name_cell = gtk.CellRendererText()
self.project_name_column = gtk.TreeViewColumn('Name', self.project_name_cell)
self.project_description_cell = gtk.CellRendererText()
self.project_description_column = gtk.TreeViewColumn('Description', self.project_description_cell)
self.project_workspace_cell = gtk.CellRendererText()
self.project_workspace_column = gtk.TreeViewColumn('Workspace', self.project_workspace_cell)
self.project_input_file_cell = gtk.CellRendererText()
self.project_input_file_column = gtk.TreeViewColumn('Input File', self.project_input_file_cell)
self.project_tree_view = gtk.TreeView() # Associate with self.project_store later, not yet!
# --- BOM tab ---
self.bom_tab_vbox = gtk.VBox(False, 0) # Second tab in notebook
self.bom_toolbar = gtk.Toolbar()
self.bom_read_input_button = gtk.ToolButton(None, "Read CSV")
self.bom_read_db_button = gtk.ToolButton(None, "Read DB")
self.bom_edit_part_button = gtk.ToolButton(None, "Edit Part")
self.bom_find_prod_button = gtk.ToolButton(None, "Find Product")
self.bom_hpane = gtk.HPaned()
self.bom_vpane = gtk.VPaned() # Goes in right side of bom_hpane
self.bom_frame = gtk.Frame("BOM") # Goes in left side of bom_hpane
self.bom_scroll_box = gtk.VBox(False, 0) # Holds bom_scroll_win and bom_radio_hbox
self.bom_scroll_win = gtk.ScrolledWindow() # Holds bomTable
# Columns: Name, Value, Device, Package, Description, MFG PN, Quantity
self.bom_store = gtk.TreeStore(str, str, str, str, str, str, str)
self.bom_name_cell = gtk.CellRendererText()
self.bom_name_column = gtk.TreeViewColumn('Name', self.bom_name_cell)
self.bom_value_cell = gtk.CellRendererText()
self.bom_value_column = gtk.TreeViewColumn('Value', self.bom_value_cell)
self.bom_device_cell = gtk.CellRendererText()
self.bom_device_column = gtk.TreeViewColumn('Device', self.bom_device_cell)
self.bom_package_cell = gtk.CellRendererText()
self.bom_package_column = gtk.TreeViewColumn('Package', self.bom_package_cell)
self.bom_description_cell = gtk.CellRendererText()
self.bom_description_column = gtk.TreeViewColumn('Description', self.bom_description_cell)
self.bom_product_cell = gtk.CellRendererText()
self.bom_product_column = gtk.TreeViewColumn('Manufacturer Part Number', self.bom_product_cell)
self.bom_quantity_cell = gtk.CellRendererText()
self.bom_quantity_column = gtk.TreeViewColumn('Quantity', self.bom_quantity_cell)
self.bom_tree_view = gtk.TreeView()
self.bom_radio_hbox = gtk.HBox(False, 0)
self.bom_radio_label = gtk.Label("Group by:")
self.bom_group_name = gtk.RadioButton(None, "Name")
self.bom_group_value = gtk.RadioButton(self.bom_group_name, "Value")
self.bom_group_product = gtk.RadioButton(self.bom_group_name, "Part Number")
self.selected_bom_part = Part("init", "init", "init", "init", "init")
self.part_info_frame = gtk.Frame("Part information") # Goes in top half of bom_vpane
self.part_info_vbox = gtk.VBox(False, 5) # Fill with HBoxes
self.part_info_product_table = gtk.Table(5, 2, False) # Product info
self.part_info_manufacturer_label = gtk.Label("Manufacturer: ")
self.part_info_manufacturer_content_label = gtk.Label(None)
self.part_info_manufacturer_pn_label = gtk.Label("Manufacturer Part Number: ")
self.part_info_manufacturer_pn_content_label = gtk.Label(None)
self.part_info_description_label = gtk.Label("Description: ")
self.part_info_description_content_label = gtk.Label(None)
self.part_info_datasheet_label = gtk.Label("Datasheet filename: ")
self.part_info_datasheet_content_label = gtk.Label(None)
self.part_info_package_label = gtk.Label("Package/case: ")
self.part_info_package_content_label = gtk.Label(None)
self.part_info_pricing_table = gtk.Table(8, 3 , False) # Price breaks
self.price_break_labels = []
self.unit_price_labels = []
self.ext_price_labels = []
self.part_info_button_hbox = gtk.HBox(False, 5)
self.part_info_scrape_button = gtk.Button("Scrape")
self.part_info_datasheet_button = gtk.Button("Datasheet")
self.part_info_listing_label = gtk.Label("Product source: ")
self.part_info_listing_combo = gtk.combo_box_new_text()
self.part_info_set_listing_button = gtk.Button("Use this listing")
self.part_info_inventory_hbox = gtk.HBox(False, 5)
self.part_info_inventory_label = gtk.Label("Inventory: ")
self.part_info_inventory_content_label = gtk.Label(None)
self.pricing_frame = gtk.Frame("Project pricing") # Goes in bottom half of bom_vpane
self.pricing_vbox = gtk.VBox(False, 5)
self.run_size_pin_label = gtk.Label("Run size: ")
#self.run_size_hbox = gtk.HBox(False, 5)
self.run_size_adjustment = gtk.Adjustment(1, 1, 99999, 1, 10, 0.0)
self.run_size_spin = gtk.SpinButton(self.run_size_adjustment, 0.5, 0)
self.run_unit_price_hbox = gtk.HBox(False, 5)
self.run_unit_price_label= gtk.Label("Per-kit BOM cost: ")
self.run_unit_price_content_label= gtk.Label("\t")
self.run_total_price_hbox = gtk.HBox(False, 5)
self.run_total_cost_label= gtk.Label("Total run cost: ")
self.run_total_cost_content_label= gtk.Label("\t")
# --- Product DB tab ---
self.db_vbox = gtk.VBox(False, 0) # Third tab in notebook
self.db_toolbar = gtk.Toolbar()
self.db_read_database_button = gtk.ToolButton(None, "Read DB")
self.db_frame = gtk.Frame("Product database")
self.db_scroll_win = gtk.ScrolledWindow()
#self.db_product_store = gtk.ListStore(str, str, int, str, str, str, str, str, str, str, str)
self.db_product_store = gtk.TreeStore(str, str, str, str, str)
#self.dbVendorCell = gtk.CellRendererText()
#self.dbVendorColumn = gtk.TreeViewColumn('Vendor', self.dbVendorCell)
#self.dbvendor_pnCell = gtk.CellRendererText()
#self.dbvendor_pnColumn = gtk.TreeViewColumn('Vendor PN', self.dbvendor_pnCell)
#self.dbInventoryCell = gtk.CellRendererText()
#self.dbInventoryColumn = gtk.TreeViewColumn('Inventory', self.dbInventoryCell)
self.db_manufacturer_cell = gtk.CellRendererText()
self.db_manufacturer_column = gtk.TreeViewColumn('Manufacturer', self.db_manufacturer_cell)
self.db_manufacturer_pn_cell = gtk.CellRendererText()
self.db_manufacturer_pn_column = gtk.TreeViewColumn('Manufacturer PN', self.db_manufacturer_pn_cell)
self.db_description_cell = gtk.CellRendererText()
self.db_description_column = gtk.TreeViewColumn('Description', self.db_description_cell)
self.db_datasheet_cell = gtk.CellRendererText()
self.db_datasheet_column = gtk.TreeViewColumn('Datasheet filename', self.db_datasheet_cell)
#self.dbCategoryCell = gtk.CellRendererText()
#self.dbCategoryColumn = gtk.TreeViewColumn('Category', self.dbCategoryCell)
#self.dbFamilyCell = gtk.CellRendererText()
#self.dbFamilyColumn = gtk.TreeViewColumn('Family', self.dbFamilyCell)
#self.dbSeriesCell = gtk.CellRendererText()
#self.dbSeriesColumn = gtk.TreeViewColumn('Series', self.dbSeriesCell)
self.db_package_cell = gtk.CellRendererText()
self.db_package_column = gtk.TreeViewColumn('Package/case', self.db_package_cell)
self.db_tree_view = gtk.TreeView()
# -------- CONFIGURATION --------
self.window.set_title("Eagle BOM Manager")
# TODO: Add project name to window title on file open
self.window.connect("delete_event", self.delete_event)
self.window.connect("destroy", self.destroy)
self.file_item.set_submenu(self.file_menu)
self.save_item.connect("activate", self.file_save_callback)
self.quit_item.connect("activate", self.destroy)
self.notebook.set_tab_pos(gtk.POS_TOP)
self.notebook.append_page(self.project_box, self.project_tab_label)
self.notebook.append_page(self.bom_tab_vbox, self.bom_tab_label)
self.notebook.append_page(self.db_vbox, self.db_tab_label)
self.notebook.set_show_tabs(True)
# ---- Project selection tab ----
self.project_new_button.connect("clicked", self.project_new_callback)
self.new_project_input_file_button.connect("clicked", self.new_project_input_file_callback)
self.project_open_button.connect("clicked", self.project_open_callback)
self.project_delete_button.connect("clicked", self.project_delete_callback)
self.new_project_name_label.set_alignment(0.0, 0.5)
self.new_project_description_label.set_alignment(0.0, 0.5)
#self.new_projectWorkspaceLabel.set_alignment(0.0, 0.5)
self.new_project_input_file_label.set_alignment(0.0, 0.5)
self.project_scroll_win.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
#self.project_tree_view.set_fixed_height_mode(True)
self.project_tree_view.set_reorderable(True)
self.project_tree_view.set_headers_clickable(True)
self.project_name_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.project_description_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.project_workspace_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.project_input_file_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.project_name_column.set_resizable(True)
self.project_description_column.set_resizable(True)
self.project_workspace_column.set_resizable(True)
self.project_input_file_column.set_resizable(True)
self.project_name_column.set_attributes(self.project_name_cell, text=0)
self.project_description_column.set_attributes(self.project_description_cell, text=1)
self.project_workspace_column.set_attributes(self.project_workspace_cell, text=2)
self.project_input_file_column.set_attributes(self.project_input_file_cell, text=3)
self.project_tree_view.append_column(self.project_name_column)
self.project_tree_view.append_column(self.project_description_column)
self.project_tree_view.append_column(self.project_workspace_column)
self.project_tree_view.append_column(self.project_input_file_column)
self.project_store_populate()
self.project_tree_view.set_model(self.project_store)
# ---- BOM tab ----
self.bom_name_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_value_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_device_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_package_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_description_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_product_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_quantity_column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.bom_name_column.set_resizable(True)
self.bom_value_column.set_resizable(True)
self.bom_device_column.set_resizable(True)
self.bom_package_column.set_resizable(True)
self.bom_description_column.set_resizable(True)
self.bom_product_column.set_resizable(True)
self.bom_quantity_column.set_resizable(True)
self.bom_name_column.set_clickable(True)
self.bom_value_column.set_clickable(True)
self.bom_device_column.set_clickable(True)
self.bom_package_column.set_clickable(True)
self.bom_description_column.set_clickable(True)
self.bom_product_column.set_clickable(True)
self.bom_quantity_column.set_clickable(True)
#self.bom_name_column.set_sort_indicator(True)
#self.bom_value_column.set_sort_indicator(True)
#self.bom_device_column.set_sort_indicator(True)
#self.bom_package_column.set_sort_indicator(True)
#self.bom_description_column.set_sort_indicator(True)
#self.bom_product_column.set_sort_indicator(True)
#self.bom_quantity_column.set_sort_indicator(True)
self.bom_name_column.set_attributes(self.bom_name_cell, text=0)
self.bom_value_column.set_attributes(self.bom_value_cell, text=1)
self.bom_device_column.set_attributes(self.bom_device_cell, text=2)
self.bom_package_column.set_attributes(self.bom_package_cell, text=3)
self.bom_description_column.set_attributes(self.bom_description_cell, text=4)
self.bom_product_column.set_attributes(self.bom_product_cell, text=5)
self.bom_quantity_column.set_attributes(self.bom_quantity_cell, text=6)
self.bom_name_column.connect("clicked", self.bom_sort_callback)
self.bom_value_column.connect("clicked", self.bom_sort_callback)
self.bom_device_column.connect("clicked", self.bom_sort_callback)
self.bom_package_column.connect("clicked", self.bom_sort_callback)
self.bom_description_column.connect("clicked", self.bom_sort_callback)
self.bom_product_column.connect("clicked", self.bom_sort_callback)
self.bom_quantity_column.connect("clicked", self.bom_sort_callback)
self.bom_tree_view.set_reorderable(True)
self.bom_tree_view.set_enable_search(True)
self.bom_tree_view.set_headers_clickable(True)
self.bom_tree_view.set_headers_visible(True)
self.bom_tree_view.append_column(self.bom_name_column)