@@ -305,20 +305,25 @@ def __init__(self, main_window, datasets_sub):
305
305
306
306
self .main_window = main_window
307
307
self .datasets_sub = datasets_sub
308
- self .dock_to_checkbox = dict ()
308
+ self .dock_to_item = dict ()
309
309
self .applet_uids = set ()
310
310
311
- self .table = QtWidgets .QTableWidget (0 , 3 )
312
- self .table .setHorizontalHeaderLabels (["Enable" , "Name" , "Command" ])
311
+ self .table = QtWidgets .QTreeWidget ()
312
+ self .table .setColumnCount (3 )
313
+ self .table .setHeaderLabels (["Enable" , "Name" , "Command" ])
313
314
self .table .setSelectionBehavior (QtWidgets .QAbstractItemView .SelectRows )
314
315
self .table .setSelectionMode (QtWidgets .QAbstractItemView .SingleSelection )
315
- self .table .horizontalHeader ().setStretchLastSection (True )
316
- self .table .horizontalHeader ().setSectionResizeMode (
317
- QtWidgets .QHeaderView .ResizeToContents )
318
- self .table .verticalHeader ().setSectionResizeMode (
316
+
317
+ self .table .header ().setStretchLastSection (True )
318
+ self .table .header ().setSectionResizeMode (
319
319
QtWidgets .QHeaderView .ResizeToContents )
320
- self .table .verticalHeader ().hide ()
321
320
self .table .setTextElideMode (QtCore .Qt .ElideNone )
321
+
322
+ self .table .setDragEnabled (True )
323
+ self .table .viewport ().setAcceptDrops (True )
324
+ self .table .setDropIndicatorShown (True )
325
+ self .table .setDragDropMode (QtWidgets .QAbstractItemView .InternalMove )
326
+
322
327
self .setWidget (self .table )
323
328
324
329
completer_delegate = _CompleterDelegate ()
@@ -327,28 +332,32 @@ def __init__(self, main_window, datasets_sub):
327
332
328
333
self .table .setContextMenuPolicy (QtCore .Qt .ActionsContextMenu )
329
334
new_action = QtWidgets .QAction ("New applet" , self .table )
330
- new_action .triggered .connect (lambda : self .new ( ))
335
+ new_action .triggered .connect (partial ( self . new_with_parent , self .new ))
331
336
self .table .addAction (new_action )
332
337
templates_menu = QtWidgets .QMenu ()
333
338
for name , template in _templates :
334
339
action = QtWidgets .QAction (name , self .table )
335
- action .triggered .connect (partial (self .new_template , template ))
340
+ action .triggered .connect (partial (
341
+ self .new_with_parent , self .new , command = template ))
336
342
templates_menu .addAction (action )
337
343
restart_action = QtWidgets .QAction ("New applet from template" , self .table )
338
344
restart_action .setMenu (templates_menu )
339
345
self .table .addAction (restart_action )
340
- restart_action = QtWidgets .QAction ("Restart selected applet" , self .table )
346
+ restart_action = QtWidgets .QAction ("Restart selected applet or group " , self .table )
341
347
restart_action .setShortcut ("CTRL+R" )
342
348
restart_action .setShortcutContext (QtCore .Qt .WidgetShortcut )
343
349
restart_action .triggered .connect (self .restart )
344
350
self .table .addAction (restart_action )
345
- delete_action = QtWidgets .QAction ("Delete selected applet" , self .table )
351
+ delete_action = QtWidgets .QAction ("Delete selected applet or group " , self .table )
346
352
delete_action .setShortcut ("DELETE" )
347
353
delete_action .setShortcutContext (QtCore .Qt .WidgetShortcut )
348
354
delete_action .triggered .connect (self .delete )
349
355
self .table .addAction (delete_action )
356
+ new_group_action = QtWidgets .QAction ("New group" , self .table )
357
+ new_group_action .triggered .connect (partial (self .new_with_parent , self .new_group ))
358
+ self .table .addAction (new_group_action )
350
359
351
- self .table .cellChanged .connect (self .cell_changed )
360
+ self .table .itemChanged .connect (self .item_changed )
352
361
353
362
def create (self , uid , name , command ):
354
363
dock = _AppletDock (self .datasets_sub , uid , name , command )
@@ -358,122 +367,218 @@ def create(self, uid, name, command):
358
367
dock .sigClosed .connect (partial (self .on_dock_closed , dock ))
359
368
return dock
360
369
361
- def cell_changed (self , row , column ):
362
- if column == 0 :
363
- item = self .table .item (row , column )
364
- if item .checkState () == QtCore .Qt .Checked :
365
- command = self .table .item (row , 2 )
366
- if command :
367
- command = command .text ()
368
- name = self .table .item (row , 1 )
369
- if name is None :
370
- name = ""
371
- else :
372
- name = name .text ()
373
- dock = self .create (item .applet_uid , name , command )
374
- item .applet_dock = dock
375
- if item .applet_geometry is not None :
376
- dock .restoreGeometry (item .applet_geometry )
377
- # geometry is now handled by main window state
378
- item .applet_geometry = None
379
- self .dock_to_checkbox [dock ] = item
380
- else :
370
+ def item_changed (self , item , column ):
371
+ if item .ty == "applet" :
372
+ if column == 0 :
373
+ if item .checkState (0 ) == QtCore .Qt .Checked :
374
+ command = item .text (2 )
375
+ if command :
376
+ name = item .text (1 )
377
+ dock = self .create (item .applet_uid , name , command )
378
+ item .applet_dock = dock
379
+ if item .applet_geometry is not None :
380
+ dock .restoreGeometry (item .applet_geometry )
381
+ # geometry is now handled by main window state
382
+ item .applet_geometry = None
383
+ self .dock_to_item [dock ] = item
384
+ else :
385
+ dock = item .applet_dock
386
+ if dock is not None :
387
+ # This calls self.on_dock_closed
388
+ dock .close ()
389
+ elif column == 1 or column == 2 :
390
+ new_value = item .text (column )
381
391
dock = item .applet_dock
382
392
if dock is not None :
383
- # This calls self.on_dock_closed
384
- dock .close ()
385
- elif column == 1 or column == 2 :
386
- new_value = self .table .item (row , column ).text ()
387
- dock = self .table .item (row , 0 ).applet_dock
388
- if dock is not None :
389
- if column == 1 :
390
- dock .rename (new_value )
391
- else :
392
- dock .command = new_value
393
+ if column == 1 :
394
+ dock .rename (new_value )
395
+ else :
396
+ dock .command = new_value
397
+ elif item .ty == "group" :
398
+ # To Qt's credit, it already does everything for us here.
399
+ pass
400
+ else :
401
+ raise ValueError
393
402
394
403
def on_dock_closed (self , dock ):
395
- checkbox_item = self .dock_to_checkbox [dock ]
396
- checkbox_item .applet_dock = None
397
- checkbox_item .applet_geometry = dock .saveGeometry ()
404
+ item = self .dock_to_item [dock ]
405
+ item .applet_dock = None
406
+ item .applet_geometry = dock .saveGeometry ()
398
407
asyncio .ensure_future (dock .terminate ())
399
- del self .dock_to_checkbox [dock ]
400
- checkbox_item .setCheckState (QtCore .Qt .Unchecked )
401
-
402
- def new (self , uid = None ):
408
+ del self .dock_to_item [dock ]
409
+ item .setCheckState (0 , QtCore .Qt .Unchecked )
410
+
411
+ def get_untitled (self ):
412
+ existing_names = set ()
413
+ def walk (wi ):
414
+ for i in range (wi .childCount ()):
415
+ cwi = wi .child (i )
416
+ existing_names .add (cwi .text (1 ))
417
+ walk (cwi )
418
+ walk (self .table .invisibleRootItem ())
419
+
420
+ i = 1
421
+ name = "untitled"
422
+ while name in existing_names :
423
+ i += 1
424
+ name = "untitled " + str (i )
425
+ return name
426
+
427
+ def new (self , uid = None , name = None , command = "" , parent = None ):
403
428
if uid is None :
404
429
uid = next (i for i in count () if i not in self .applet_uids )
405
430
assert uid not in self .applet_uids , uid
406
431
self .applet_uids .add (uid )
407
432
408
- row = self .table .rowCount ()
409
- self .table .insertRow (row )
410
- checkbox = QtWidgets .QTableWidgetItem ()
411
- checkbox .setFlags (QtCore .Qt .ItemIsSelectable |
412
- QtCore .Qt .ItemIsUserCheckable |
413
- QtCore .Qt .ItemIsEnabled )
414
- checkbox .setCheckState (QtCore .Qt .Unchecked )
415
- checkbox .applet_uid = uid
416
- checkbox .applet_dock = None
417
- checkbox .applet_geometry = None
418
- self .table .setItem (row , 0 , checkbox )
419
- self .table .setItem (row , 1 , QtWidgets .QTableWidgetItem ())
420
- self .table .setItem (row , 2 , QtWidgets .QTableWidgetItem ())
421
- return row
422
-
423
- def new_template (self , template ):
424
- row = self .new ()
425
- self .table .item (row , 2 ).setText (template )
433
+ if name is None :
434
+ name = self .get_untitled ()
435
+ item = QtWidgets .QTreeWidgetItem (["" , name , command ])
436
+ item .ty = "applet"
437
+ item .setFlags (QtCore .Qt .ItemIsSelectable |
438
+ QtCore .Qt .ItemIsUserCheckable |
439
+ QtCore .Qt .ItemIsEditable |
440
+ QtCore .Qt .ItemIsDragEnabled |
441
+ QtCore .Qt .ItemNeverHasChildren |
442
+ QtCore .Qt .ItemIsEnabled )
443
+ item .setCheckState (0 , QtCore .Qt .Unchecked )
444
+ item .applet_uid = uid
445
+ item .applet_dock = None
446
+ item .applet_geometry = None
447
+ item .setIcon (0 , QtWidgets .QApplication .style ().standardIcon (
448
+ QtWidgets .QStyle .SP_ComputerIcon ))
449
+ if parent is None :
450
+ self .table .addTopLevelItem (item )
451
+ else :
452
+ parent .addChild (item )
453
+ return item
454
+
455
+ def new_group (self , name = None , parent = None ):
456
+ if name is None :
457
+ name = self .get_untitled ()
458
+ item = QtWidgets .QTreeWidgetItem (["" , name ])
459
+ item .ty = "group"
460
+ item .setFlags (QtCore .Qt .ItemIsSelectable |
461
+ QtCore .Qt .ItemIsEditable |
462
+ QtCore .Qt .ItemIsUserCheckable |
463
+ QtCore .Qt .ItemIsAutoTristate |
464
+ QtCore .Qt .ItemIsDragEnabled |
465
+ QtCore .Qt .ItemIsDropEnabled |
466
+ QtCore .Qt .ItemIsEnabled )
467
+ item .setIcon (0 , QtWidgets .QApplication .style ().standardIcon (
468
+ QtWidgets .QStyle .SP_DirIcon ))
469
+ if parent is None :
470
+ self .table .addTopLevelItem (item )
471
+ else :
472
+ parent .addChild (item )
473
+ return item
474
+
475
+ def new_with_parent (self , cb , ** kwargs ):
476
+ parent = None
477
+ selection = self .table .selectedItems ()
478
+ if selection :
479
+ parent = selection [0 ]
480
+ if parent .ty == "applet" :
481
+ parent = parent .parent ()
482
+ if parent is not None :
483
+ parent .setExpanded (True )
484
+ cb (parent = parent , ** kwargs )
426
485
427
486
def restart (self ):
428
- selection = self .table .selectedRanges ()
487
+ selection = self .table .selectedItems ()
429
488
if selection :
430
- row = selection [0 ].topRow ()
431
- dock = self .table .item (row , 0 ).applet_dock
432
- if dock is not None :
433
- asyncio .ensure_future (dock .restart ())
489
+ item = selection [0 ]
490
+ def walk (wi ):
491
+ if wi .ty == "applet" :
492
+ dock = wi .applet_dock
493
+ if dock is not None :
494
+ asyncio .ensure_future (dock .restart ())
495
+ elif wi .ty == "group" :
496
+ for i in range (wi .childCount ()):
497
+ walk (wi .child (i ))
498
+ else :
499
+ raise ValueError
500
+ walk (item )
434
501
435
502
def delete (self ):
436
- selection = self .table .selectedRanges ()
503
+ selection = self .table .selectedItems ()
437
504
if selection :
438
- row = selection [0 ].topRow ()
439
- item = self .table .item (row , 0 )
440
- dock = item .applet_dock
441
- if dock is not None :
442
- # This calls self.on_dock_closed
443
- dock .close ()
444
- self .applet_uids .remove (item .applet_uid )
445
- self .table .removeRow (row )
505
+ item = selection [0 ]
506
+
507
+ def recursive_delete (wi ):
508
+ if wi .ty == "applet" :
509
+ dock = wi .applet_dock
510
+ if dock is not None :
511
+ # This calls self.on_dock_closed
512
+ dock .close ()
513
+ self .applet_uids .remove (wi .applet_uid )
514
+ elif wi .ty == "group" :
515
+ for i in range (wi .childCount ()):
516
+ recursive_delete (wi .child (i ))
517
+ else :
518
+ raise ValueError
519
+ recursive_delete (item )
520
+
521
+ parent = item .parent ()
522
+ if parent is None :
523
+ parent = self .table .invisibleRootItem ()
524
+ parent .removeChild (item )
446
525
447
526
async def stop (self ):
448
- for row in range (self .table .rowCount ()):
449
- dock = self .table .item (row , 0 ).applet_dock
450
- if dock is not None :
451
- await dock .terminate ()
527
+ async def walk (wi ):
528
+ for row in range (wi .childCount ()):
529
+ cwi = wi .child (row )
530
+ if cwi .ty == "applet" :
531
+ dock = cwi .applet_dock
532
+ if dock is not None :
533
+ await dock .terminate ()
534
+ elif cwi .ty == "group" :
535
+ await walk (cwi )
536
+ else :
537
+ raise ValueError
538
+ await walk (self .table .invisibleRootItem ())
452
539
453
- def save_state (self ):
540
+ def save_state_item (self , wi ):
454
541
state = []
455
- for row in range (self .table .rowCount ()):
456
- uid = self .table .item (row , 0 ).applet_uid
457
- enabled = self .table .item (row , 0 ).checkState () == QtCore .Qt .Checked
458
- name = self .table .item (row , 1 ).text ()
459
- command = self .table .item (row , 2 ).text ()
460
- geometry = self .table .item (row , 0 ).applet_geometry
461
- if geometry is not None :
462
- geometry = bytes (geometry )
463
- state .append ((uid , enabled , name , command , geometry ))
542
+ for row in range (wi .childCount ()):
543
+ cwi = wi .child (row )
544
+ if cwi .ty == "applet" :
545
+ uid = cwi .applet_uid
546
+ enabled = cwi .checkState (0 ) == QtCore .Qt .Checked
547
+ name = cwi .text (1 )
548
+ command = cwi .text (2 )
549
+ geometry = cwi .applet_geometry
550
+ if geometry is not None :
551
+ geometry = bytes (geometry )
552
+ state .append (("applet" , uid , enabled , name , command , geometry ))
553
+ elif cwi .ty == "group" :
554
+ name = cwi .text (1 )
555
+ expanded = cwi .isExpanded ()
556
+ state_child = self .save_state_item (cwi )
557
+ state .append (("group" , name , expanded , state_child ))
558
+ else :
559
+ raise ValueError
464
560
return state
465
561
562
+ def save_state (self ):
563
+ return self .save_state_item (self .table .invisibleRootItem ())
564
+
565
+ def restore_state_item (self , state , parent ):
566
+ for wis in state :
567
+ if wis [0 ] == "applet" :
568
+ _ , uid , enabled , name , command , geometry = wis
569
+ item = self .new (uid , name , command , parent = parent )
570
+ if geometry is not None :
571
+ geometry = QtCore .QByteArray (geometry )
572
+ item .applet_geometry = geometry
573
+ if enabled :
574
+ item .setCheckState (0 , QtCore .Qt .Checked )
575
+ elif wis [0 ] == "group" :
576
+ _ , name , expanded , state_child = wis
577
+ item = self .new_group (name , parent = parent )
578
+ item .setExpanded (expanded )
579
+ self .restore_state_item (state_child , item )
580
+ else :
581
+ raise ValueError ("Invalid item state: " + str (wis [0 ]))
582
+
466
583
def restore_state (self , state ):
467
- for uid , enabled , name , command , geometry in state :
468
- row = self .new (uid )
469
- item = QtWidgets .QTableWidgetItem ()
470
- item .setText (name )
471
- self .table .setItem (row , 1 , item )
472
- item = QtWidgets .QTableWidgetItem ()
473
- item .setText (command )
474
- self .table .setItem (row , 2 , item )
475
- if geometry is not None :
476
- geometry = QtCore .QByteArray (geometry )
477
- self .table .item (row , 0 ).applet_geometry = geometry
478
- if enabled :
479
- self .table .item (row , 0 ).setCheckState (QtCore .Qt .Checked )
584
+ self .restore_state_item (state , None )
0 commit comments