-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathalttoolbar_type.py
1686 lines (1354 loc) · 58.7 KB
/
alttoolbar_type.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
# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2015 - 2020 David Mohammed <fossfreedom@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import xml.etree.ElementTree as ET
from datetime import datetime, date
from xml.etree.ElementTree import SubElement
import rb
from alttoolbar_controller import AltAndroidController
from alttoolbar_controller import AltCoverArtBrowserController
from alttoolbar_controller import AltCoverArtPlaySourceController
from alttoolbar_controller import AltErrorsController
from alttoolbar_controller import AltGenericController
from alttoolbar_controller import AltLastFMController
from alttoolbar_controller import AltMusicLibraryController
from alttoolbar_controller import AltPlaylistController
from alttoolbar_controller import AltPodcastController
from alttoolbar_controller import AltQueueController
from alttoolbar_controller import AltRadioController
from alttoolbar_controller import AltSoundCloudController
from alttoolbar_controller import AltStandardLocalController
from alttoolbar_controller import AltStandardOnlineController
from alttoolbar_preferences import CoverLocale
from alttoolbar_preferences import GSetting
from alttoolbar_rb3compat import gtk_version
from alttoolbar_repeat import Repeat
from alttoolbar_sidebar import AltToolbarSidebar
from alttoolbar_widget import Slider
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import Gio
from gi.repository import Gtk
from gi.repository import Pango
from gi.repository import RB
class AT(object):
@staticmethod
def ToolbarRequestCallback(toolbar_type, box=None):
"""
Callback method to obtain the type of toolbar together with the
Gtk.Box where new UI elements can be added
:param toolbar_type: AltToolbarBase derived object
:param box: Gtk.Box or None if the toolbar does not have a UI where
new objects can be added
:return:
"""
return toolbar_type, box
class AltToolbarBase(GObject.Object):
"""
base for all toolbar types - never instantiated by itself
"""
setup_completed = GObject.property(type=bool,
default=False)
# if changed to true then
# setup_completed observers called back
source_toolbar_visible = GObject.property(type=bool, default=True)
def __init__(self):
"""
Initialises the object.
"""
super(AltToolbarBase, self).__init__()
# remember details about when an entryview has been processed
self._process_entryview = {}
folder = RB.user_cache_dir() + "/alternate-toolbar"
if not os.path.exists(folder):
os.makedirs(folder)
self._entryview_filename = folder + "/entryview_db.xml"
self._save_cols_loop = 0
db_version = "1"
try:
# if the db has not been deleted or is screwed up or is an older
# version than expected then
# assume via the except we are starting from a clean db
self._entryview_tree = ET.parse(self._entryview_filename)
self._entryview_root = self._entryview_tree.getroot()
db = self._entryview_root.find("database")
if db.text != db_version:
raise ValueError("wrong database version")
except:
text = """
<root>
<pages>
</pages>
</root>
"""
self._entryview_root = ET.fromstring(text)
db = SubElement(self._entryview_root, "database")
db.text = db_version
self._entryview_tree = ET.ElementTree(self._entryview_root)
# bind the source-toolbar gsettings
gs = GSetting()
plugin_settings = gs.get_setting(gs.Path.PLUGIN)
plugin_settings.bind(gs.PluginKey.SOURCE_TOOLBAR, self,
'source_toolbar_visible',
Gio.SettingsBindFlags.DEFAULT)
self._async_functions = [] # array of functions to callback once the
# toolbar has been setup
self.connect('notify::setup-completed', self._on_setup_completed)
def initialise(self, plugin):
"""
one off initialisation call
:param plugin is the plugin reference
"""
self.plugin = plugin
self.shell = plugin.shell
self.find = plugin.find
# finally - complete the headerbar setup after the database has fully
# loaded because
# rhythmbox has everything initiated at this point.
self.startup_completed = False
# self.shell.props.db.connect('load-complete', self.on_load_complete)
# fire event anyway - scenario is when plugin is first activated post
# rhythmbox having started
def delayed(*args):
if self.shell.props.selected_page:
self.startup_completed = True
self.on_startup()
return False
else:
return True
GLib.timeout_add(100, delayed)
def get_custom_box(self):
"""
:return: Gtk.Box
"""
return None
def post_initialise(self):
"""
one off post initialisation call
"""
tool_action = 'ToggleSourceMediaToolbar'
action = self.plugin.toggle_action_group.get_action(tool_action)
action.set_active(self.source_toolbar_visible)
def on_startup(self, *args):
"""
call after RB has completed its initialisation and selected the first
view
:param args:
:return:
"""
self.startup_completed = True
self.reset_categories_pos(self.shell.props.selected_page)
self.reset_toolbar(self.shell.props.selected_page)
# lets hide the ghastly floating bar in RB 3.4.3
cssdata = """
.floating-bar {
opacity: 0;
}
"""
cssprovider = Gtk.CssProvider.new()
cssprovider.load_from_data(cssdata.encode())
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
self.shell.props.window.props.screen,
cssprovider,
Gtk.STYLE_PROVIDER_PRIORITY_USER,
)
self.reset_entryview(self.shell.props.selected_page)
if self.plugin.prefer_dark_theme:
settings = Gtk.Settings.get_default()
settings.set_property('gtk-application-prefer-dark-theme', True)
def cleanup(self):
"""
initiate a toolbar cleanup of resources and changes made to rhythmbox
:return:
"""
for page in self._process_entryview:
self.disconnect(self._process_entryview[page]['size'])
self.disconnect(self._process_entryview[page]['changed'])
self.purge_builder_content()
def on_search_toggle(self):
"""
use the toolbar search
"""
pass
def set_visible(self, visible):
"""
change the visibility of the toolbar
:param bool
"""
pass
def show_cover(self, visible):
"""
change the visibility of the toolbar coverart
:param bool
"""
pass
def display_song(self, visible):
"""
change the visibility of the song label on the toolbar
:param bool
"""
pass
def play_control_change(self, player, playing):
"""
control the display of various play-controls
:param player is the shell-player
:param playing bool as to whether a track is being played
"""
pass
def purge_builder_content(self):
"""
one off cleanup routine called when the plugin in deactivated
"""
pass
def show_slider(self, visible):
"""
show or hide the slider (progress bar)
:param visible is a bool
"""
pass
def enable_slider(self, toggle):
"""
enable or disable the slider (progress bar)
:param toggle is a bool
"""
pass
def reset_categories_pos(self, page):
"""
whenever a source changes this resets the source categories position
reflect the changed source
:param page - RBDisplayPage
"""
print("reset categories position")
if not page:
print("no page")
return
if not hasattr(page.props, 'show_browser'):
print("no browser")
return
if not self.plugin.horiz_categories:
print("not horizontal")
return
propertyview = self.find(page, 'RBPropertyView', 'by_name')
if propertyview is None:
return
parent = propertyview.get_parent()
if isinstance(parent, Gtk.Paned):
print("paned")
parent.set_orientation(Gtk.Orientation.HORIZONTAL)
else:
print("not paned")
pane = parent.get_parent()
print(pane)
parent.set_orientation(Gtk.Orientation.VERTICAL)
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
def reset_entryview(self, page):
"""
whenever a source changes this resets the source entryview to
reflect the changed source
:param page - RBDisplayPage
"""
print("reset entryview")
if not page:
print("no page")
return
# workaround for GTK Crashing issues due to the user locale
# non english locales (german for example) cause RB to
# seg fault when moving columns - sadly need to disable
# column moving capability for non-english locale users :(
names = GLib.get_language_names()
if ("en" not in names[0]):
return
try:
entryview = page.get_entry_view()
if not entryview:
print("no entry view")
return
except:
# some pages dont have the method to call!
return
try:
treeview = entryview.get_child()
except:
treeview = self.find(entryview, 'GtkTreeView', 'by_name')
# in RB v3.4.3 an RBEntryView doesnt have a child property...
# plus the GtkTreeview is now hidden inside two further
# containers - so we grab the tree view the hard-way of searching
# for the Object Type
def move_col(*args):
cols = treeview.get_columns()
# treeview.set_reorderable(True)
current_cols = []
base_col = None
base_col_found = False
for col in cols:
title = col.props.title
if title is not None and title.strip() != "":
if not base_col_found:
base_col = cols[cols.index(col) - 1]
base_col_found = True
print(title)
col.set_reorderable(True)
current_cols.append(col)
if page in self._process_entryview:
# disconnect previous signal handler if have been connected
# before otherwise we'll trigger stuff when moving columns
treeview.disconnect(self._process_entryview[page]['changed'])
treeview.disconnect(self._process_entryview[page]['size'])
# now move columns around depending upon saved values
safe_name = self._safe_string(type(page).__name__)
lookup = "pages/page[@name='" + safe_name + "']"
element = self._entryview_root.find(lookup)
if element is not None:
# we've got something remembered to lets move cols around
remembered_col_titles = element.text.split(',')
remembered_cols = []
for title in remembered_col_titles:
compare = title[1:-1]
for col in current_cols:
if col.props.title == compare:
remembered_cols.append(col)
break
for i in range(len(remembered_cols)):
for col in current_cols:
if col.props.title == remembered_cols[i].props.title:
if current_cols.index(col) != i:
print(i, col.props.title)
if i == 0:
treeview.move_column_after(col, base_col)
else:
pos = i - 1
treeview.move_column_after(col,
remembered_cols[
pos])
break
# now reset column widths
# for col in current_cols:
# safe_col_name = self._safe_string(
# col.props.title)
# lookup = "pages/" + safe_name + "[
# @column='" + \
# safe_col_name + "']"
# col_node = self._entryview_root.find(
# lookup)
# if col_node is not None:
# col.set_fixed_width(int(
# col_node.get("width")))
# now connect new signal handler
ids = {}
ids['changed'] = treeview.connect('columns-changed',
self._entryview_column_changed,
page)
ids['size'] = treeview.connect('size-allocate',
self._entryview_size_allocate, page)
self._process_entryview[page] = ids
# add a short delay otherwise RB will move after us nulling our
# achievement
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 10,
move_col)
def _safe_string(self, s):
return ''.join([i for i in s if i.isalpha()])
def _entryview_size_allocate(self, treeview, allocation, page):
self._entryview_column_changed(treeview, page)
def _entryview_column_changed(self, treeview, page):
# we basically don't want to process column-changed signals
# when closing because these are fired by RB during the entry-view
# cleanup & columns being deleted
# so we work around this by looping for .5 secs before saving...
# if we don't finish looping we assume that RB is closing and thus
# dont really want to save ... yes a bit of a funny but its the best
# we can do since RB doesnt have a close signal ... and just waiting
# on the windows close event doesnt work because File-Quit cleans up
# before actually closing.
def _save_cols(*args):
self._save_cols_loop += 1
if self._save_cols_loop <= 6:
return True
self._save_cols_loop = 0
self._save_entryview_cols(treeview, page)
return False
if self._save_cols_loop == 0:
self._save_cols_loop = 1
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100,
_save_cols)
else:
self._save_cols_loop = 1
def _save_entryview_cols(self, treeview, page):
print("entryview column changed")
print(page)
def quoted_string(array):
return ','.join("'{0}'".format(x) for x in array)
safe_name = self._safe_string(type(page).__name__)
lookup = "pages/page[@name='" + safe_name + "']"
node = self._entryview_root.find(lookup)
pages = self._entryview_root.find("pages")
if node is None:
print("new node")
node = SubElement(pages, 'page')
node.set("name", safe_name)
arr = []
cols = treeview.get_columns()
for col in cols:
if col.props.title is not None and col.props.title != "":
arr.append(col.props.title)
# print (col.get_width())
safe_col_name = self._safe_string(col.props.title)
lookup = "pages/" + safe_name + "[@column='" + safe_col_name \
+ "']"
col_node = self._entryview_root.find(lookup)
if col_node is None:
col_node = SubElement(pages, safe_name)
col_node.set("column", safe_col_name)
col_node.set("width", str(col.get_width()))
if len(arr) < 2:
# nothing to do so quit before writing
return
output = quoted_string(arr)
print(output)
node.text = output
self._indent_xml(self._entryview_root)
self._entryview_tree.write(self._entryview_filename,
xml_declaration=True)
def _indent_xml(self, elem, level=0, more_sibs=False):
i = "\n"
if level:
i += (level - 1) * ' '
num_kids = len(elem)
if num_kids:
if not elem.text or not elem.text.strip():
elem.text = i + " "
if level:
elem.text += ' '
count = 0
for kid in elem:
self._indent_xml(kid, level + 1, count < num_kids - 1)
count += 1
if not elem.tail or not elem.tail.strip():
elem.tail = i
if more_sibs:
elem.tail += ' '
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
if more_sibs:
elem.tail += ' '
def reset_toolbar(self, page):
"""
whenever a source changes this resets the toolbar to reflect the
changed source
:param page - RBDisplayPage
"""
print("reset toolbar")
if not page:
print("no page")
return
toolbar = self.find(page, 'RBSourceToolbar', 'by_name')
if toolbar:
print("found")
toolbar.set_visible(self.source_toolbar_visible)
else:
print("not found")
self.plugin.emit('toolbar-visibility', self.source_toolbar_visible)
def setup_completed_async(self, async_function):
"""
toolbars will callback once the setup has completed
:param async_function: function callback
:return:
"""
if self.setup_completed:
async_function(AT.ToolbarRequestCallback(self,
self.get_custom_box()))
else:
self._async_functions.append(async_function)
def _on_setup_completed(self, *args):
"""
one-off callback anybody who has registered to be notified when a
toolbar has been completely setup
:param args:
:return:
"""
if self.setup_completed:
for callback_func in self._async_functions:
callback_func(AT.ToolbarRequestCallback(self,
self.get_custom_box()))
def source_toolbar_visibility(self, visibility):
"""
called to toggle the source toolbar
"""
print("source_bar_visibility")
self.source_toolbar_visible = visibility
# not self.source_toolbar_visible
self.plugin.on_page_change(self.shell.props.display_page_tree,
self.shell.props.selected_page)
class AltToolbarStandard(AltToolbarBase):
"""
standard RB toolbar
"""
__gtype_name = 'AltToolbarStandard'
def __init__(self):
"""
Initialises the object.
"""
super(AltToolbarStandard, self).__init__()
def post_initialise(self):
self.volume_button = self.find(self.plugin.rb_toolbar,
'GtkVolumeButton', 'by_id')
self.volume_button.set_visible(self.plugin.volume_control)
action = self.plugin.toggle_action_group.get_action('ToggleToolbar')
action.set_active(not self.plugin.start_hidden)
self.set_visible(not self.plugin.start_hidden)
self.setup_completed = True
def set_visible(self, visible):
self.plugin.rb_toolbar.set_visible(visible)
class AltToolbarShared(AltToolbarBase):
"""
shared components for the compact and headerbar toolbar types
"""
def __init__(self):
"""
Initialises the object.
"""
super(AltToolbarShared, self).__init__()
# Prepare Album Art Displaying
self.album_art_db = GObject.new(RB.ExtDB, name="album-art")
what, width, height = Gtk.icon_size_lookup(Gtk.IconSize.SMALL_TOOLBAR)
self.icon_width = width
self.cover_pixbuf = None
self._controllers = {}
self._tooltip_exceptions = ['album_cover']
self._moved_controls = []
def initialise(self, plugin):
super(AltToolbarShared, self).initialise(plugin)
ui = rb.find_plugin_file(plugin, 'ui/alttoolbar.ui')
cl = CoverLocale()
builder = Gtk.Builder()
builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN)
builder.add_from_file(ui)
self.load_builder_content(builder)
self.connect_builder_content(builder)
self._controllers['generic'] = AltGenericController(self)
# every potential source should have its own controller - we use this
# to categorise the source and provide specific capability for
# inherited classes where a controller is not specified then a generic
# controller is used i.e. use add_controller method to add a controller
self.add_controller(AltMusicLibraryController(self))
self.add_controller(AltSoundCloudController(self))
self.add_controller(AltCoverArtBrowserController(self))
self.add_controller(AltCoverArtPlaySourceController(self))
self.add_controller(AltQueueController(self))
self.add_controller(AltStandardOnlineController(self))
self.add_controller(AltStandardLocalController(self))
self.add_controller(AltRadioController(self))
self.add_controller(AltLastFMController(self))
self.add_controller(AltPlaylistController(self))
self.add_controller(AltErrorsController(self))
self.add_controller(AltPodcastController(self))
self.add_controller(AltAndroidController(self))
# support RTL
for control, icon_name in \
[(self.prev_button, 'media-skip-backward-symbolic'),
(self.play_button, 'media-playback-start-symbolic'),
(self.next_button, 'media-skip-forward-symbolic')]:
image = control.get_child()
icon_name = self.request_rtl_icon(control, icon_name)
image.set_from_icon_name(icon_name, image.props.icon_size)
# now move current RBDisplayPageTree to listview stack
display_tree = self.shell.props.display_page_tree
self.display_tree_parent = display_tree.get_parent()
self.display_tree_parent.remove(display_tree)
self.stack = Gtk.Stack()
tran_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT
self.stack.set_transition_type(tran_type)
self.stack.set_transition_duration(1000)
image_name = 'view-list-symbolic'
box_listview = Gtk.Box()
box_listview.pack_start(display_tree, True, True, 0)
# box_listview.show_all()
self.stack.add_named(box_listview, "listview")
self.stack.child_set_property(box_listview, "icon-name", image_name)
self.stack.show_all()
self.display_tree_parent.pack1(self.stack, True, True)
# if 1==2: #self.plugin.enhanced_sidebar:
toolbar = self.find(display_tree, 'GtkToolbar', 'by_name')
# context = toolbar.get_style_context()
# context.add_class('toolbar')
box = self.find(toolbar, 'GtkBox', 'by_name')
# box.props.margin_top = 2
# box.props.margin_bottom = 0
# box.props.margin_left = 5
context = box.get_style_context()
context.add_class('linked')
# parent = box.get_parent()
# parent.remove(box)
# parent_toolbar = toolbar.get_parent()
# parent_toolbar.remove(toolbar)
# display_tree.attach(box, 0, 10, 1 ,1 )
# child, new-parent, old-parent
# self._moved_controls.append((box, display_tree, parent))
# self._moved_controls.append((toolbar, None, parent_toolbar))
# find the actual GtkTreeView in the RBDisplayTree and remove it
self.rbtree = self.find(display_tree, 'GtkTreeView', 'by_name')
self.rbtreeparent = self.rbtree.get_parent()
self.rbtreeparent.remove(self.rbtree)
self.sidebar = None
def post_initialise(self):
super(AltToolbarShared, self).post_initialise()
cl = CoverLocale()
cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
self.volume_button.props.value = \
self.shell.props.shell_player.props.volume
self.volume_button.bind_property("value",
self.shell.props.shell_player,
"volume",
Gio.SettingsBindFlags.DEFAULT)
self.volume_button.set_visible(self.plugin.volume_control)
self.volume_button.set_relief(Gtk.ReliefStyle.NORMAL)
child = self.volume_button.get_child()
child.set_margin_left(5)
child.set_margin_right(5)
if self.plugin.inline_label:
self.song_box.remove(self.song_button_label)
self.song_progress = Slider(self.shell.props.shell_player)
self.song_progress_box.pack_start(self.song_progress, False, True, 1)
# Bring Builtin Actions to plugin
for (a, b) in ((self.play_button, "play"),
(self.prev_button, "play-previous"),
(self.next_button, "play-next"),
(self.repeat_toggle, "play-repeat"),
(self.shuffle_toggle, "play-shuffle")):
a.set_action_name("app." + b)
if b == "play-repeat" or b == "play-shuffle":
# for some distros you need to set the target_value
# for others this would actually disable the action
# so work around this by testing if the action is disabled
# then reset the action
# https://gitlab.gnome.org/GNOME/gtk/issues/939
# the above issue is why you see actionhelper mismatch
# errors
a.set_action_target_value(GLib.Variant("b", True))
if not a.get_sensitive():
a.set_detailed_action_name("app." + b)
# The Play-Repeat button is subject to the plugins Repeat All/one song
# capability
self._repeat = Repeat(self.shell, self.repeat_toggle)
if gtk_version() >= 3.12:
self.cover_popover = Gtk.Popover.new(self.album_cover)
image = Gtk.Image.new()
self.cover_popover.add(image)
self._popover_inprogress = 0
self.cover_popover.set_modal(False)
self.cover_popover.connect('leave-notify-event',
self._on_cover_popover_mouse_over)
self.cover_popover.connect('enter-notify-event',
self._on_cover_popover_mouse_over)
# detect when mouse moves out of the cover image
# (it has a parent eventbox)
box = self.album_cover_eventbox
box.connect('leave-notify-event',
self._on_cover_popover_mouse_over)
box.connect('enter-notify-event',
self._on_cover_popover_mouse_over)
cl.switch_locale(cl.Locale.RB)
def on_startup(self, *args):
super(AltToolbarShared, self).on_startup(*args)
if self.plugin.enhanced_sidebar:
self.sidebar = AltToolbarSidebar(self, self.rbtree)
self.sidebar.show_all()
self.rbtreeparent.add(self.sidebar)
else:
self.rbtreeparent.add(self.rbtree)
# self.shell.add_widget(self.rbtree, RB.ShellUILocation.SIDEBAR,
# expand=True, fill=True)
def register_moved_control(self, child, old_parent, new_parent=None):
"""
convenience function to save the GTK child & parents when they are
moved.
we use this info to cleanup when quitting RB - we need to move
stuff back because
otherwise there are random crashes due to memory deallocation issues
:param child: GTK Widget
:param old_parent: original GTK container that the child was moved from
:param new_parent: new GTK container that the child was added to (may
just have removed without moving)
:return:
"""
# store as a tuple: child, new-parent, old-parent
self._moved_controls.append((child, new_parent, old_parent))
def cleanup(self):
"""
extend
:return:
"""
super(AltToolbarShared, self).cleanup()
if self.sidebar:
self.sidebar.cleanup()
self.display_tree_parent.remove(self.stack)
self.display_tree_parent.pack1(self.shell.props.display_page_tree)
if self.sidebar:
self.rbtreeparent.remove(self.sidebar) # remove our sidebar
self.rbtreeparent.add(self.rbtree) # add the original GtkTree view
print("####")
# child, new-parent, old-parent
for child, new_parent, old_parent in reversed(self._moved_controls):
if new_parent:
new_parent.remove(child)
print(child)
print(new_parent)
print(old_parent)
if isinstance(old_parent, Gtk.Grid):
print("attaching to grid")
old_parent.attach(child, 0, 0, 1, 1)
else:
print("adding to parent")
old_parent.add(child)
def add_controller(self, controller):
"""
register a new controller
"""
if controller not in self._controllers:
self._controllers[controller] = controller
def is_controlled(self, source):
"""
determine if the source has a controller
return bool, controller
if no specific controller (False) then the generic controller
returned
"""
if source in self._controllers:
return True, self._controllers[source]
# loop through controllers to find one that is most applicable
for controller_type in self._controllers:
if self._controllers[controller_type].valid_source(source):
return True, self._controllers[controller_type]
return False, self._controllers['generic']
def show_cover_tooltip(self, tooltip):
if (self.cover_pixbuf is not None):
scale = self.cover_pixbuf.scale_simple(300, 300,
GdkPixbuf.InterpType.HYPER)
if gtk_version() >= 3.12:
if self.cover_popover.get_visible():
return False
image = self.cover_popover.get_child()
image.set_from_pixbuf(scale)
self.cover_popover.show_all()
else:
tooltip.set_icon(scale)
return True
else:
return False
def _on_cover_popover_mouse_over(self, widget, eventcrossing):
if eventcrossing.type == Gdk.EventType.ENTER_NOTIFY:
if self._popover_inprogress == 0:
self._popover_inprogress = 1
else:
self._popover_inprogress = 2
self._popover_inprogress_count = 0
print("enter")
else:
print("exit")
self._popover_inprogress = 3
# print (eventcrossing.type)
def delayed(*args):
if self._popover_inprogress == 3:
self._popover_inprogress_count += 1
if self._popover_inprogress_count < 5:
return True
self.cover_popover.hide()
self._popover_inprogress = 0
return False
else:
return True
if self._popover_inprogress == 1:
print("addding timeout")
self._popover_inprogress = 2
GLib.timeout_add(100, delayed)
def show_slider(self, visibility):
self.song_box.set_visible(visibility)
def enable_slider(self, toggle):
self.song_progress.set_sensitive(toggle)
def display_song(self, entry):
self.entry = entry
self.cover_pixbuf = None
self.album_cover.clear()
if self.plugin.inline_label:
ret = self._inline_progress_label(entry)
else:
ret = self._combined_progress_label(entry)
if ret:
key = entry.create_ext_db_key(RB.RhythmDBPropType.ALBUM)
self.album_art_db.request(key,
self.display_song_album_art_callback,
entry)
def _inline_progress_label(self, entry):
if (entry is None):
# self.song_button_label.set_text("")
self.inline_box.set_visible(False)
return False
self.inline_box.set_visible(True)
db = self.shell.props.db
stream_title = \
db.entry_request_extra_metadata(entry,
RB.RHYTHMDB_PROP_STREAM_SONG_TITLE)
stream_artist = \
db.entry_request_extra_metadata(entry,
RB.RHYTHMDB_PROP_STREAM_SONG_ARTIST)
def set_labels(title, artist):
for child in self.inline_box:
self.inline_box.remove(child)
self.song_title = Gtk.Label()
self.song_title.set_markup(title)
self.song_title.set_ellipsize(Pango.EllipsizeMode.END)