From ec495ad3bf03e0376acfdc4ed3cf3bb6da9915d2 Mon Sep 17 00:00:00 2001 From: Quentin Sculo Date: Thu, 17 Sep 2009 16:31:41 +0200 Subject: [PATCH] redesign of layout Window options and init, and implement new window options - "pos" and "size" options can be relative to the width/height of the screen by using a percentage (example : pos=50%x20%). - If a percentage is used for "pos", the window will be centered on this position by default. This can be changed by appending a percentage relative to the size of the window. For example "pos=50%-50%x0+10%" will put the window at x= screenwidth/2 - windowwidth/2 and y= 0 + windowheight/10 - add transparent=1 window option, it requires Cairo and a compositing window manager to work - add insensitive=1 window option, it makes the window completely insensitive to mouse or keyboard. - it is now possible to specify options with the name of a layout example : OpenCustom(NameOfLayout(pos=100%x50%)) will open the window at the bottom center - add "uniqueid" and "ifexist" window options. uniqueid is a string id that default to the layout id. ifexist define the action to take when attempting to create a window with the same uniqueid as an existing window, it can be : 'toggle' : to close the existing window 'present' : to bring the existing window to the front 'replace' : to close the window and open a new window --- debian/control | 2 +- gmusicbrowser.pl | 85 ++++---- gmusicbrowser.spec | 2 +- gmusicbrowser_layout.pm | 417 ++++++++++++++++++++-------------------- layouts | 4 +- 5 files changed, 248 insertions(+), 262 deletions(-) diff --git a/debian/control b/debian/control index fd54e064..272c586d 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,7 @@ Standards-Version: 3.7.3 Package: gmusicbrowser Architecture: all Depends: perl, libgtk2-perl, libgtk2.0-0 (>= 2.6) -Recommends: liblocale-gettext-perl (>= 1.04), libnet-dbus-perl, libgtk2-trayicon-perl, libgstreamer-perl, libdigest-crc-perl +Recommends: liblocale-gettext-perl (>= 1.04), libnet-dbus-perl, libgtk2-trayicon-perl, libgstreamer-perl, libdigest-crc-perl, libcairo-perl Suggests: mplayer, mpg321, vorbis-tools, alsa-utils, libgtk2-mozembed-perl Description: jukebox for large collections of mp3/ogg/flac/mpc files Uses GStreamer, mpg321/ogg123/flac123 or mplayer for playback. It has easy diff --git a/gmusicbrowser.pl b/gmusicbrowser.pl index b27f5662..2737e9d4 100755 --- a/gmusicbrowser.pl +++ b/gmusicbrowser.pl @@ -618,8 +618,8 @@ sub ConvertSize our ($RandomMode,$SortFields,$ListMode); our ($SongID,$Recent,$RecentPos,$Queue); our $QueueAction=''; our ($Position,$ChangedID,$ChangedPos,@NextSongs,$NextFileToPlay); -our ($MainWindow,$BrowserWindow,$ContextWindow,$FullscreenWindow); my $OptionsDialog; -our $QueueWindow; my $TrayIcon; +our ($MainWindow,$FullscreenWindow); my $OptionsDialog; +my $TrayIcon; my %Editing; #used to keep track of opened song properties dialog and lyrics dialog our $PlayTime; our ($StartTime,$StartedAt,$PlayingID,$PlayedPartial); @@ -867,14 +867,14 @@ sub GetIconThemesList Rewind => [\&Rewind, _"Rewind",_"Number of seconds",qr/^\d+$/], Seek => [sub {SkipTo($_[1])}, _"Seek",_"Number of seconds",qr/^\d+$/], Stop => [\&Stop, _"Stop"], - Browser => [\&Playlist, _"Open Browser"], + Browser => [\&OpenBrowser, _"Open Browser"], OpenQueue => [\&EditQueue, _"Open Queue window"], - OpenSearch => [sub { Layout::Window->new($Options{LayoutS}); }, _"Open Search window"], - OpenContext => [sub { Layout::Window->new('Context');}, _"Open Context window"], + OpenSearch => [sub { Layout::Window->new($Options{LayoutS}, uniqueid=>'Search'); }, _"Open Search window"], + OpenContext => [\&ContextWindow, _"Open Context window"], OpenCustom => [sub { Layout::Window->new($_[1]); }, _"Open Custom window",_"Name of layout", sub { TextCombo->new( Layout::get_layout_list() ); }], PopupCustom => [sub { PopupLayout($_[1],$_[0]); }, _"Popup Custom window",_"Name of layout", sub { TextCombo::Tree->new( Layout::get_layout_list() ); }], CloseWindow => [sub { $_[0]->get_toplevel->close_window if $_[0];}, _"Close Window"], - SetPlayerLayout => [sub { SetOption(Layout=>$_[1]); set_layout(); },_"Set player window layout",_"Name of layout", sub { TextCombo::Tree->new( Layout::get_layout_list('G') ); }, ], + SetPlayerLayout => [sub { SetOption(Layout=>$_[1]); CreateMainWindow(); },_"Set player window layout",_"Name of layout", sub { TextCombo::Tree->new( Layout::get_layout_list('G') ); }, ], OpenPref => [\&PrefDialog, _"Open Preference window"], OpenSongProp => [sub { DialogSongProp($SongID) if defined $SongID }, _"Edit Current Song Properties"], EditSelectedSongsProperties => [sub { my $songlist=GetSonglist($_[0]) or return; my @IDs=$songlist->GetSelectedIDs; DialogSongsProp(@IDs) if @IDs; }, _"Edit selected song properties"], @@ -1058,7 +1058,7 @@ sub forksystem ActivatePlugin($_,'startup') for grep $Options{'PLUGIN_'.$_}, sort keys %Plugins; our $Tooltips=Gtk2::Tooltips->new; -$MainWindow=Layout::Window->new($CmdLine{layout}||$Options{Layout}); +CreateMainWindow( $CmdLine{layout}||$Options{Layout} ); &ShowHide if $CmdLine{hide}; SkipTo($PlayTime) if $PlayTime; #done only now because of gstreamer @@ -2536,32 +2536,28 @@ sub IdleLoop return $IdleLoop; } -sub Playlist -{ if ($BrowserWindow) - { if ($_[0]{toggle}) {$BrowserWindow->close_window} - else {$BrowserWindow->present} - } - else - { $BrowserWindow=Layout::Window->new($Options{LayoutB}); - $BrowserWindow->signal_connect(destroy => sub { $BrowserWindow=undef; }); - } +sub OpenBrowser +{ OpenSpecialWindow('Browser'); } sub ContextWindow -{ if ($ContextWindow) - { if ($_[0]{toggle}) {$ContextWindow->close_window} - else {$ContextWindow->present} - } - else - { $ContextWindow=Layout::Window->new('Context'); - $ContextWindow->signal_connect(destroy => sub { $ContextWindow=undef; }); - } +{ OpenSpecialWindow('Context'); +} +sub EditQueue +{ OpenSpecialWindow('Queue'); } +sub OpenSpecialWindow +{ my ($type,$toggle)=@_; + my $layout= $type eq 'Browser' ? $Options{LayoutB} : $type; + my $ifexist= $toggle ? 'toggle' : 'present'; + Layout::Window->new($layout, ifexist => $ifexist, uniqueid=>$type); +} + sub ToggleFullscreenLayout { if ($FullscreenWindow) { $FullscreenWindow->close_window; } else - { $FullscreenWindow=Layout::Window->new($Options{LayoutF},undef,'UseDefaultState'); + { $FullscreenWindow=Layout::Window->new($Options{LayoutF},fullscreen=>1); $FullscreenWindow->signal_connect(destroy => sub { $FullscreenWindow=undef; }); if ($Options{StopScreensaver} && findcmd('xdg-screensaver')) { my $h={ XID => $FullscreenWindow->window->XID}; @@ -2583,23 +2579,12 @@ sub ToggleFullscreenLayout } } -sub EditQueue -{ if ($QueueWindow) - { if ($_[0]{toggle}) {$QueueWindow->close_window} - else {$QueueWindow->present} - } - else - { $QueueWindow=Layout::Window->new('Queue'); - $QueueWindow->signal_connect(destroy => sub { $QueueWindow=undef; }); - } -} - sub WEditList { my $name=$_[0]; my ($window)=grep exists $_->{editing_listname} && $_->{editing_listname} eq $name, Gtk2::Window->list_toplevels; if ($window) { $window->present; return; } $SongList::Common::EditList=$name; #list that will be used by SongList/SongTree in 'editlist' mode - $window=Layout::Window->new('EditList',UseDefaultState=>1,KeepSize=>1); + $window=Layout::Window->new('EditList', 'pos'=>undef); $SongList::Common::EditList=undef; $window->{editing_listname}=$name; Watch($window, SavedLists => sub #close window if the list is deleted, update title if renamed @@ -5069,7 +5054,7 @@ sub PrefAudio if (exists $PlayPacks{Play_GST}) { my $hbox2=NewPrefCombo(gst_sink => Play_GST->supported_sinks, text => _"output device :", sizeg1=>$sg1, sizeg2=> $sg2); my $EQbut=Gtk2::Button->new(_"Open Equalizer"); - $EQbut->signal_connect(clicked => sub {Layout::Window->new('Equalizer');}); + $EQbut->signal_connect(clicked => sub { OpenSpecialWindow('Equalizer'); }); my $EQcheck=NewPrefCheckButton(gst_use_equalizer => _"Use Equalizer", sub { HasChanged('Equalizer'); }); $sg1->add_widget($EQcheck); $sg2->add_widget($EQbut); @@ -5213,7 +5198,7 @@ sub PrefLayouts my $sg1=Gtk2::SizeGroup->new('horizontal'); my $sg2=Gtk2::SizeGroup->new('horizontal'); my $layoutT=NewPrefCombo(LayoutT=> Layout::get_layout_list('T'), text => _"Tray tip window layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1); - my $layout =NewPrefCombo(Layout => Layout::get_layout_list('G'), text =>_"Player window layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1, cb => \&set_layout); + my $layout =NewPrefCombo(Layout => Layout::get_layout_list('G'), text =>_"Player window layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1, cb => sub {CreateMainWindow();}, ); my $layoutB=NewPrefCombo(LayoutB=> Layout::get_layout_list('B'), text =>_"Browser window layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1); my $layoutF=NewPrefCombo(LayoutF=> Layout::get_layout_list('F'), text =>_"Full screen layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1); my $layoutS=NewPrefCombo(LayoutS=> Layout::get_layout_list('S'), text =>_"Search window layout :", sizeg1=>$sg1,sizeg2=>$sg2, tree=>1); @@ -5229,11 +5214,12 @@ sub PrefLayouts return $vbox; } -sub set_layout -{ my $old=$MainWindow; - $old->SaveOptions; - $MainWindow=Layout::Window->new( $Options{Layout} ); - $old->destroy; +sub CreateMainWindow +{ my $layout=shift; + $layout=$Options{Layout} unless defined $layout; + $MainWindow->{quitonclose}=0 if $MainWindow; + $MainWindow=Layout::Window->new( $layout, uniqueid=> 'MainWindow', ifexist => 'replace'); + $MainWindow->{quitonclose}=1; } sub PrefTags @@ -6013,7 +5999,7 @@ sub PresentWindow sub PopupLayout { my ($layout,$widget)=@_; - return if $widget && Layout::Window::Popup::find_window($widget); + return if $widget && $widget->{PoppedUpWindow}; my $popup=Layout::Window::Popup->new($layout,$widget); } @@ -6078,7 +6064,7 @@ sub TrayMenuPopup { label=> _"Settings", code => \&PrefDialog, stockicon => 'gtk-preferences' }, { label=> _"Quit", code => \&Quit, stockicon => 'gtk-quit' }, ); - my $traytip=Layout::Window::Popup::find_window($TrayIcon->child,$_[0]); + my $traytip=$TrayIcon->child->{PoppedUpWindow}; $traytip->DestroyNow if $traytip; $TrayIcon->{NoTrayTip}=1; my $m=PopupContextMenu(\@TrayMenu,{}); @@ -6136,10 +6122,11 @@ sub IsWindowVisible return $visible; } sub ShowHide -{ if ( IsWindowVisible($MainWindow) ) +{ my (@windows)=grep $_->isa('Layout::Window') && $_->{showhide} && $_!=$MainWindow, Gtk2::Window->list_toplevels; + if ( IsWindowVisible($MainWindow) ) { #hide #warn "hiding\n"; - for my $win ($MainWindow,$BrowserWindow,$ContextWindow) + for my $win ($MainWindow,@windows) { next unless $win; $win->{saved_position}=join 'x',$win->get_position; $win->iconify; @@ -6154,7 +6141,7 @@ sub ShowHide my $screen=Gtk2::Gdk::Screen->get_default; my $scrw=$screen->get_width; my $scrh=$screen->get_height; - for my $win ($ContextWindow,$BrowserWindow,$MainWindow) + for my $win (@windows,$MainWindow) { next unless $win; my ($x,$y)= $win->{saved_position} ? split('x', delete $win->{saved_position}) : $win->get_position; my ($w,$h)= $win->get_size; diff --git a/gmusicbrowser.spec b/gmusicbrowser.spec index c0f9fb71..a78b878a 100644 --- a/gmusicbrowser.spec +++ b/gmusicbrowser.spec @@ -11,7 +11,7 @@ Packager: Quentin Sculo Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildArch: noarch Requires: perl >= 5.8, gtk2 >= 2.6.0, perl-Gtk2, perl-Gtk2-TrayIcon, perl(Locale::gettext) >= 1.04, perl-GStreamer -Requires(hint): mpg123, vorbis-tools, alsa-utils, perl-Gtk2-WebKit, perl-Gtk2-MozEmbed, perl-Net-DBus, gstreamer0.10-lame, gstreamer0.10-plugins-ugly, gstreamer0.10-plugins-bad, perl-Digest-CRC +Requires(hint): mpg123, vorbis-tools, alsa-utils, perl-Gtk2-WebKit, perl-Gtk2-MozEmbed, perl-Net-DBus, gstreamer0.10-lame, gstreamer0.10-plugins-ugly, gstreamer0.10-plugins-bad, perl-Digest-CRC, perl-Cairo AutoReq: no AutoProv: no diff --git a/gmusicbrowser_layout.pm b/gmusicbrowser_layout.pm index 6362bf19..389f1f8b 100644 --- a/gmusicbrowser_layout.pm +++ b/gmusicbrowser_layout.pm @@ -37,7 +37,7 @@ my @MenuQueue= my @MainMenu= ( {label => _"Add files or folders",code => sub {::ChooseAddPath(0)}, stockicon => 'gtk-add' }, {label => _"Settings", code => \&::PrefDialog, stockicon => 'gtk-preferences' }, - {label => _"Open Browser", code => \&::Playlist, stockicon => 'gmb-playlist' }, + {label => _"Open Browser", code => \&::OpenBrowser,stockicon => 'gmb-playlist' }, {label => _"Open Context window",code => \&::ContextWindow, stockicon => 'gtk-info'}, {label => _"Switch to fullscreen mode",code => \&::ToggleFullscreenLayout, stockicon => 'gtk-fullscreen'}, {label => _"About", code => \&::AboutDialog,stockicon => 'gtk-about' }, @@ -82,8 +82,8 @@ our %Widgets= options => 'toggle', stock => 'gmb-playlist', tip => _"Open Browser window", - activate=> sub {::Playlist($_[0]{toggle})}, - click3 => sub {Layout::Window->new($::Options{LayoutB});}, + activate=> sub { ::OpenSpecialWindow('Browser',$_[0]{toggle}); }, + click3 => sub { ::OpenSpecialWindow('Browser'); }, }, BContext => { class => 'Layout::Button', @@ -91,15 +91,22 @@ our %Widgets= options => 'toggle', stock => 'gtk-info', tip => _"Open Context window", - activate=> sub {::ContextWindow($_[0]{toggle})}, - click3 => sub {Layout::Window->new('Context');}, + activate=> sub { ::OpenSpecialWindow('Context',$_[0]{toggle}); }, + click3 => sub { ::OpenSpecialWindow('Context'); }, + }, + OpenQueue => + { class => 'Layout::Button', + stock => 'gmb-queue-window', + tip => _"Open Queue window", + options => 'toggle', + activate=> sub { ::OpenSpecialWindow('Queue',$_[0]{toggle}); }, }, Pref => { class => 'Layout::Button', stock => 'gtk-preferences', tip => _"Edit Settings", activate=> \&::PrefDialog, - click3 => sub {Layout::Window->new($::Options{Layout});}, + click3 => sub {Layout::Window->new($::Options{Layout});}, #mostly for debugging purpose click2 => \&::AboutDialog, }, Quit => @@ -563,13 +570,6 @@ our %Widgets= QueueActions => { class => 'QueueActions', }, - OpenQueue => - { class => 'Layout::Button', - stock => 'gmb-queue-window', - tip => _"Open Queue window", - options => 'toggle', - activate=> sub {::EditQueue($_[0]{toggle})}, - }, Fullscreen => { class => 'Layout::Button', stock => 'gtk-fullscreen', @@ -791,15 +791,9 @@ sub InitLayout $self->{KeyBindings}=::make_keybindingshash($boxes->{KeyBindings}) if $boxes->{KeyBindings}; $self->{widgets}={}; $self->{global_options}{default_group}=$self->{group}; - for (qw/SkinPath SkinFile DefaultFont Window/) + for (qw/SkinPath SkinFile DefaultFont/) { $self->{global_options}{$_}=$boxes->{$_} if exists $boxes->{$_}; } - my $border_width=2; #default border width - if (my $wopt=$self->{global_options}{Window}) - { $border_width=$1 if $wopt=~m/\bborderwidth=(\d+)\b/; - $self->set_skip_pager_hint(1) && $self->set_skip_taskbar_hint(1) if $wopt=~m/\bskip=1\b/; - } - $self->set_border_width($border_width); my $mainwidget= $self->CreateWidgets($boxes,$opt2); return unless $mainwidget; @@ -1467,8 +1461,37 @@ use base 'Gtk2::Window'; sub new { my ($class,$layout,%options)=@_; my $fallback=delete $options{fallback} || 'Lists, Library & Context'; + my $opt0={}; + if (my $opt= $layout=~m/^[^(]+\(.*=/) + { ($layout,$opt0)= $layout=~m/^([^(]+)\((.*)\)$/; #separate layout id and options + $opt0= ::ParseOptions($opt0); + } + unless (exists $Layout::Layouts{$layout}) + { warn "Layout '$layout' not found, using '$fallback' instead\n"; + $layout=$fallback; #FIXME if not a player window + $Layout::Layouts{$layout} ||= { VBmain=>'Label(text="Error : fallback layout not found")' }; #create an error layout if fallback not found + } + my $opt2=$::Options{Layouts}{$layout}; + $opt2||= Layout::GetDefaultLayoutOptions($layout); + my $opt1=::ParseOptions( $Layout::Layouts{$layout}{Window}||'' ); + %options= ( borderwidth=>2, %$opt1, %{$opt2->{Window}||{}}, %options, %$opt0 ); + #warn "window options (layout=$layout) :\n";warn " $_ => $options{$_}\n" for sort keys %options; + + my $uniqueid= $options{uniqueid} || 'layout='.$layout; + # ifexist=toggle => if a window with same uniqueid exist it will be closed + # ifexist=present => if a window with same uniqueid exist it presented + if (my $mode=$options{ifexist}) + { my ($window)=grep $_->isa('Layout::Window') && $_->{uniqueid} eq $uniqueid, Gtk2::Window->list_toplevels; + if ($window) + { if ($mode eq 'toggle' && !$window->{quitonclose}) { $window->close_window; return } + elsif ($mode eq 'replace' && !$window->{quitonclose}) { $window->close_window; } + elsif ($mode eq 'present') { $window->present; return } + } + } + my $wintype= delete $options{wintype} || 'toplevel'; my $self=bless Gtk2::Window->new($wintype), $class; + $self->{uniqueid}= $uniqueid; $self->set_role($layout); $self->{options}=\%options; $self->{name}='Window'; @@ -1502,86 +1525,44 @@ sub new # ::TRUE; # } # ); - unless (exists $Layout::Layouts{$layout}) - { warn "Layout '$layout' not found, using '$fallback' instead\n"; - $layout=$fallback; #FIXME if not a player window - $Layout::Layouts{$layout} ||= { VBmain=>'Label(text="Error : fallback layout not found")' }; #create an error layout if fallback not found - } - my $opt2=$::Options{Layouts}{$layout}; - $opt2||= Layout::GetDefaultLayoutOptions($layout); $self->InitLayout($layout,$opt2); - $self->SetWindowOptions($opt2->{Window}); + $self->SetWindowOptions(\%options); if (my $skin=$Layout::Layouts{$layout}{Skin}) { $self->set_background_skin($skin) } $self->init; ::HasChanged('HiddenWidgets'); #$self->set_opacity($self->{opacity}) if exists $self->{opacity} && $self->{opacity}!=1; return $self; } + sub init { my $self=$_[0]; - #$self->set_position();#doesn't work before show, at least with sawfish - $self->move($self->{x},$self->{y}) if defined $self->{x}; - my @hidden; + if ($self->{options}{transparent}) + { eval { require Cairo unless $::useCairo; $::useCairo=1; }; + if ($::useCairo) + { make_transparent($self); + } + else { warn "Error : can't load the Cairo perl module => can't make the window transparent\n" } + } + $self->child->show_all; #needed to get the true size of the window + $self->child->realize; # + { my @hidden; @hidden=keys %{ $self->{hidden} } if $self->{hidden}; my $widgets=$self->{widgets}; push @hidden,$widgets->{$_}{need_hide} for grep $widgets->{$_}{need_hide}, keys %$widgets; @hidden=map $widgets->{$_}, @hidden; - $_->show_all for @hidden; $_->hide for @hidden; - @hidden=grep !$_->get_no_show_all, @hidden; - $_->set_no_show_all(1) for @hidden; - $self->show_all; - $_->set_no_show_all(0) for @hidden; -# if (my $h=$self->{hidden}) #restore hidden states -# { $self->{widgets}{$_}->hide for keys %$h; } - $self->move($self->{x},$self->{y}) if defined $self->{x}; + } + #$self->set_position();#doesn't work before show, at least with sawfish + $self->Resize if $self->{size}; + my ($x,$y)= $self->Position; + $self->move($x,$y) if defined $x; + $self->show; + $self->move($x,$y) if defined $x; $self->parse_geometry( delete $::CmdLine{geometry} ) if $::CmdLine{geometry}; -} - -sub set_background_skin -{ my ($self,$skin)=@_; - my ($file,$crop,$resize)=split /:/,$skin; - #$self->set_decorated(0); - $self->{pixbuf}=Skin::_load_skinfile($file,$crop,$self->{global_options}); - return unless $self->{pixbuf}; - $self->{resizeparam}=$resize; - $self->{skinsize}='0x0'; - $self->signal_connect(style_set => sub {warn "style set : @_" if $::debug;$_[0]->set_style($_[2]);} ,$self->get_style); #FIXME find the cause of these signals, seems related to stock icons - $self->signal_connect(configure_event => \&resize_skin_cb); - #Gtk2::Gdk::Window->set_debug_updates(1); - #$self->queue_draw; - my $rc_style= Gtk2::RcStyle->new; - #$rc_style->bg_pixmap_name($_,'') for qw/normal selected prelight insensitive active/; - $rc_style->bg_pixmap_name('normal',''); - my @children=($self->child); - while (my $widget=shift @children) - { push @children, $widget->get_children if $widget->isa('Gtk2::Container'); - $widget->modify_style($rc_style) unless $widget->no_window; + if ($self->{options}{insensitive}) + { my $mask=Gtk2::Gdk::Bitmap->create_from_data(undef,'',1,1); + $self->input_shape_combine_mask($mask,0,0); } - $self->set_app_paintable(1); -} -sub resize_skin_cb #FIXME needs to add a delay to better deal with a burst of resize events -{ my ($self,$event)=@_; - my ($w,$h)=($event->width,$event->height); - return 0 if $w.'x'.$h eq $self->{skinsize}; -# ::IdleDo('0_resize_back_skin'.$self,1000,sub { -#Glib::Timeout->add(80,sub { -# warn 1; my $self=$_[0];my ($w,$h)=$self->window->get_size; - my $pb=Skin::_resize($self->{pixbuf},$self->{resizeparam},$w,$h); - return 0 unless $pb; - #my ($pixmap,$mask)=$pb->render_pixmap_and_mask(1); #leaks X memory for Gtk2 <1.146 or <1.153 - my $mask=Gtk2::Gdk::Bitmap->create_from_data($self->window,'',$w,$h); - $pb->render_threshold_alpha($mask,0,0,0,0,-1,-1,1); - $self->shape_combine_mask($mask,0,0); - my $pixmap=Gtk2::Gdk::Pixmap->new($self->window,$w,$h,-1); - $pb->render_to_drawable($pixmap, Gtk2::Gdk::GC->new($self->window), 0,0,0,0,-1,-1,'none',0,0); - $self->window->set_back_pixmap($pixmap,0); - $self->{skinsize}=$w.'x'.$h; - $self->queue_draw; -#0; -# },$self) if $self->{skinsize}; -#$self->{skinsize}=''; - return 0; } sub layout_name @@ -1592,7 +1573,7 @@ sub layout_name sub close_window { my $self=shift; $self->SaveOptions; - unless ($self == $::MainWindow) { $_->destroy for values %{$self->{widgets}}; $self->destroy; return } + unless ($self->{quitonclose}) { $_->destroy for values %{$self->{widgets}}; $self->destroy; return } if ($::Options{UseTray} && $::Options{CloseToTray}) { &::ShowHide; return 1} else { &::Quit } } @@ -1629,35 +1610,25 @@ sub SaveWindowOptions return \%wstate; } sub SetWindowOptions -{ my ($self,$wopt)=@_; +{ my ($self,$opt)=@_; my $layouthash= $Layout::Layouts{ $self->{layout} }; - my ($x,$y,$width,$height)=(-1,-1); - if ($wopt) - { if ($self->{options}{UseDefaultState}) - { my $default= Layout::GetDefaultLayoutOptions($self->{layout}); - $default->{size}=$wopt->{size} if $self->{options}{KeepSize}; - $wopt= $default; #replace options by default - } - ($x,$y)=split 'x',$wopt->{pos} if $wopt->{pos}; - ($width,$height)=split 'x',$wopt->{size} if $wopt->{size}; - $self->stick if $wopt->{sticky}; - $self->fullscreen, $width=$height=undef if $wopt->{fullscreen}; - $self->set_keep_above(1) if $wopt->{ontop}; - $self->set_keep_below(1) if $wopt->{below}; - $self->set_decorated(0) if $wopt->{nodecoration}; - $self->set_skip_pager_hint(1) if $wopt->{skippager}; - $self->set_skip_taskbar_hint(1) if $wopt->{skiptaskbar}; - #$self->{opacity}=$wopt->{opacity} if defined $wopt->{opacity}; - $self->{hidden}={ $wopt->{hidden}=~m/(\w+)(?::?(\d+x\d+))?/g } if $wopt->{hidden}; - } - if ($width) { $self->resize($width,$height); } + if ($opt->{fullscreen}) { $self->fullscreen; } + else + { $self->{size}=$opt->{size}; + #window position in format numberxnumber number can be a % of screen size + $self->{pos}=$opt->{pos}; + } + $self->stick if $opt->{sticky}; + $self->set_keep_above(1) if $opt->{ontop}; + $self->set_keep_below(1) if $opt->{below}; + $self->set_decorated(0) if $opt->{nodecoration}; + $self->set_skip_pager_hint(1) if $opt->{skippager}; + $self->set_skip_taskbar_hint(1) if $opt->{skiptaskbar}; + #$self->{opacity}=$opt->{opacity} if defined $opt->{opacity}; + $self->{hidden}={ $opt->{hidden}=~m/(\w+)(?::?(\d+x\d+))?/g } if $opt->{hidden}; + + $self->set_border_width($self->{options}{borderwidth}); $self->set_gravity($layouthash->{gravity}) if $layouthash->{gravity}; - if (my $scrn=Gtk2::Gdk::Screen->get_default) - {$x=-1 if $x>$scrn->get_width || $y>$scrn->get_height} - if ($x>-1 && $y>-1) - { $self->move($x,$y); - $self->{x}=$x; $self->{y}=$y; #move before show_all rarely works, so save pos to set it later - } my $title=$layouthash->{Title} || _"%S by %a"; $title=~s/^"(.*)"$/$1/; if (my @l=::UsedFields($title)) @@ -1678,6 +1649,116 @@ sub UpdateWindowTitle } } +sub Resize +{ my $self=shift; + my ($w,$h)= split 'x',delete $self->{size}; + return unless defined $h; + my $screen=$self->get_screen; + my $monitor=$screen->get_monitor_at_window($self->window); + my (undef,undef,$monitorwidth,$monitorheight)=$screen->get_monitor_geometry($monitor)->values; + $w= $1*$monitorwidth/100 if $w=~m/(\d+)%/; + $h= $1*$monitorwidth/100 if $h=~m/(\d+)%/; + $w=1 if $w<1; + $h=1 if $h<1; + $self->resize($w,$h); +} + +sub Position +{ my $self=shift; + my $pos=delete $self->{pos}; + return unless $pos; #format : 100x100 50%x100% -100x-100 500-100% x 500-50% + my ($x,$xalign,$y,$yalign)= $pos=~m/([+-]?\d+%?)(?:([+-]\d+)%)?\s*x\s*([+-]?\d+%?)(?:([+-]\d+)%)?/; + my $h=$self->size_request->height; # height of window to position + my $w=$self->size_request->width; # width of window to position + my $screen=$self->get_screen; + my $monitor=$screen->get_monitor_at_window($self->window); + my ($xmin,$ymin,$monitorwidth,$monitorheight)=$screen->get_monitor_geometry($monitor)->values; + $xalign= $x=~m/%/ ? 50 : 0 unless defined $xalign; + $yalign= $y=~m/%/ ? 50 : 0 unless defined $yalign; + $x= $monitorwidth*$1/100 if $x=~m/(-?\d+)%/; + $y= $monitorheight*$1/100 if $y=~m/(-?\d+)%/; + $x= $monitorwidth-$x if $x<0; + $y= $monitorheight-$y if $y<0; + $x-= $xalign*$w/100; + $y-= $yalign*$h/100; + $x=0 if $x<0; $x=$monitorwidth if $x>$monitorwidth; + $y=0 if $y<0; $y=$monitorheight if $y>$monitorheight; + $x+=$xmin; + $y+=$ymin; + return $x,$y; +} + +sub make_transparent +{ my @children=($_[0]); + while (my $widget=shift @children) + { push @children, $widget->get_children if $widget->isa('Gtk2::Container'); + unless ($widget->no_window) + { $widget->set_colormap($widget->get_screen->get_rgba_colormap); + $widget->set_app_paintable(1); + $widget->signal_connect(expose_event => \&transparent_expose_cb); + } + if ($widget->isa('Gtk2::container')) + { $widget->signal_connect(add => sub { make_transparent($_[1]); } ); + } + } +} +sub transparent_expose_cb #use Cairo +{ my ($w,$event)=@_; + my $cr=Gtk2::Gdk::Cairo::Context->create($event->window); + $cr->set_operator('source'); + $cr->set_source_rgba(0, 0, 0, 0); + $cr->rectangle($event->area); + $cr->fill; + return 0; #send expose to children +} + + +sub set_background_skin +{ my ($self,$skin)=@_; + my ($file,$crop,$resize)=split /:/,$skin; + #$self->set_decorated(0); + $self->{pixbuf}=Skin::_load_skinfile($file,$crop,$self->{global_options}); + return unless $self->{pixbuf}; + $self->{resizeparam}=$resize; + $self->{skinsize}='0x0'; + $self->signal_connect(style_set => sub {warn "style set : @_" if $::debug;$_[0]->set_style($_[2]);} ,$self->get_style); #FIXME find the cause of these signals, seems related to stock icons + $self->signal_connect(configure_event => \&resize_skin_cb); + #Gtk2::Gdk::Window->set_debug_updates(1); + #$self->queue_draw; + my $rc_style= Gtk2::RcStyle->new; + #$rc_style->bg_pixmap_name($_,'') for qw/normal selected prelight insensitive active/; + $rc_style->bg_pixmap_name('normal',''); + my @children=($self->child); + while (my $widget=shift @children) + { push @children, $widget->get_children if $widget->isa('Gtk2::Container'); + $widget->modify_style($rc_style) unless $widget->no_window; + } + $self->set_app_paintable(1); +} +sub resize_skin_cb #FIXME needs to add a delay to better deal with a burst of resize events +{ my ($self,$event)=@_; + my ($w,$h)=($event->width,$event->height); + return 0 if $w.'x'.$h eq $self->{skinsize}; +# ::IdleDo('0_resize_back_skin'.$self,1000,sub { +#Glib::Timeout->add(80,sub { +# warn 1; my $self=$_[0];my ($w,$h)=$self->window->get_size; + my $pb=Skin::_resize($self->{pixbuf},$self->{resizeparam},$w,$h); + return 0 unless $pb; + #my ($pixmap,$mask)=$pb->render_pixmap_and_mask(1); #leaks X memory for Gtk2 <1.146 or <1.153 + my $mask=Gtk2::Gdk::Bitmap->create_from_data($self->window,'',$w,$h); + $pb->render_threshold_alpha($mask,0,0,0,0,-1,-1,1); + $self->shape_combine_mask($mask,0,0); + my $pixmap=Gtk2::Gdk::Pixmap->new($self->window,$w,$h,-1); + $pb->render_to_drawable($pixmap, Gtk2::Gdk::GC->new($self->window), 0,0,0,0,-1,-1,'none',0,0); + $self->window->set_back_pixmap($pixmap,0); + $self->{skinsize}=$w.'x'.$h; + $self->queue_draw; +#0; +# },$self) if $self->{skinsize}; +#$self->{skinsize}=''; + return 0; +} + package Layout::Window::Popup; use Gtk2; our @ISA; @@ -1686,13 +1767,13 @@ BEGIN {push @ISA,'Layout','Layout::Window';} sub new { my ($class,$layout,$widget)=@_; $layout||=$::Options{LayoutT}; - my $self=Layout::Window::new($class,$layout, wintype=>'popup',UseDefaultState=>1,fallback=>'info'); #'info' is the fallback layout + my $self=Layout::Window::new($class,$layout, wintype=>'popup', 'pos'=>undef, size=>undef, fallback=>'full with buttons', popped_from=>$widget); if ($widget) - { $self->{popped_from}=$widget; + { ::weaken( $widget->{PoppedUpWindow}=$self ); $self->set_screen($widget->get_screen); #$self->set_transient_for($widget->get_toplevel); - $self->move( ::windowpos($self,$widget) ); + #$self->move( ::windowpos($self,$widget) ); $self->signal_connect(enter_notify_event => \&CancelDestroy); } else { $self->set_position('mouse'); } @@ -1712,20 +1793,25 @@ sub init $frame->set_shadow_type('out'); $child->set_border_width($self->get_border_width); $self->set_border_width(0); - ##$self->set_type_hint('tooltip'); #TEST ##$self->set_type_hint('notification'); #TEST #$self->set_focus_on_map(0); #$self->set_accept_focus(0); #? - $self->child->show_all; #needed to get the true size of the window - $self->child->realize; # $self->signal_connect(leave_notify_event => sub { $_[0]->StartDestroy if $_[1]->detail ne 'inferior';0; }); + $self->SUPER::init; +} + +sub Position +{ my $self=shift; + if ( my $widget= delete $self->{options}{popped_from}) + { return ::windowpos($self,$widget); + } } sub Popup { my ($widget,$addtimeout)=@_; - my $self= find_window($widget); + my $self= $widget->{PoppedUpWindow}; $addtimeout=0 if $self && !$self->{destroy_timeout}; #don't add timeout if there wasn't already one $self ||= Layout::Window::Popup->new($widget->{hover_layout},$widget); return 0 unless $self; @@ -1738,7 +1824,7 @@ sub set_hover { my $widget=$_[0]; #$widget->add_events([qw/enter-notify-mask leave-notify-mask/]); $widget->signal_connect(enter_notify_event => - sub { if (!find_window($widget)) + sub { if (!$widget->{PoppedUpWindow}) { my $delay=$widget->{hover_delay}||1000; $widget->{hover_timeout}||= Glib::Timeout->add($delay,\&Popup,$widget); } @@ -1747,16 +1833,11 @@ sub set_hover }); $widget->signal_connect(leave_notify_event => \&CancelPopup ); } -sub find_window -{ my $widget=shift; - my ($self)=grep $_->{popped_from} && $_->{popped_from}==$widget, Gtk2::Window->list_toplevels; - return $self; -} sub CancelPopup { my $widget=shift; - if (my $t=delete $widget->{hover_timeout}) { Glib::Source->remove($t); } - if (my $self=find_window($widget)) { $self->StartDestroy } + if (my $t=delete $widget->{hover_timeout}) { Glib::Source->remove($t); } + if (my $self=$widget->{PoppedUpWindow}) { $self->StartDestroy } } sub CancelDestroy { my $self=shift; @@ -1775,88 +1856,6 @@ sub DestroyNow 0; } -=needsCairo -package Layout::Window::OSD; #TEST not used yet #Layout::Window::OSD->new() -use Gtk2; -our @ISA; -BEGIN {push @ISA,'Layout','Layout::Window';} - -sub new -{ my ($class,$layout)=@_; - $layout||=$::Options{LayoutT}; - my $self=Layout::Window::new($class,$layout,wintype=>'popup',UseDefaultState=>1); - $self->move( position($self) ); - $self->show; - Glib::Timeout->add( 5000,sub {$self->destroy;0} ); - return $self; -} -sub init -{ my $self=$_[0]; - if (1) - { my @children=($self); - while (my $widget=shift @children) - { push @children, $widget->get_children if $widget->isa('Gtk2::Container'); - unless ($widget->no_window) - { $widget->set_colormap($widget->get_screen->get_rgba_colormap); - $widget->set_app_paintable(1); - $widget->signal_connect(expose_event => \&transparent_expose_cb); - } - } - } - if (0) #failed attempt at making it input-invisible - { my @children=($self); - while (my $widget=shift @children) - { push @children, $widget->get_children if $widget->isa('Gtk2::Container'); - unless ($widget->no_window) - { $widget->realize; - $widget->window->set_events(['exposure-mask']); - } - } - } - - $self->child->show_all; #needed to get the true size of the window - $self->child->realize; # - if (1) #make the window input-invisible - { $self->realize; - my $mask=Gtk2::Gdk::Bitmap->create_from_data(undef,'',1,1); - $self->input_shape_combine_mask($mask,0,0); - } -} -sub transparent_expose_cb -{ my ($w,$event)=@_; - use Cairo; - my $cr=Gtk2::Gdk::Cairo::Context->create($event->window); - $cr->set_operator('source'); - $cr->set_source_rgba(0, 0, 0, 0); - $cr->rectangle($event->area); - $cr->fill; - return 0; #send expose to children -} - -sub position #FIXME improve -{ my ($win)=@_; - my $h=$win->size_request->height; # height of window to position - my $w=$win->size_request->width; # width of window to position - my $screen=$win->get_screen; - my $monitor=$screen->get_monitor_at_window($win->window); - my ($xmin,$ymin,$monitorwidth,$monitorheight)=$screen->get_monitor_geometry($monitor)->values; - my $xmax=$xmin + $monitorwidth; - my $ymax=$ymin + $monitorheight; - my $x=$xmin + $monitorwidth/2; - my $y=$ymin + $monitorheight; - - my $ycenter=0; - if ($x+$w/2 < $xmax && $x-$w/2 >$xmin){ $x-=int($w/2); } # centered - elsif ($x+$w > $xmax) { $x=$xmax-$w; $x=$xmin if $x<$xmin } # right side - else { $x=$xmin; } # left side - if ($ycenter && $y+$h/2 < $ymax && $y-$h/2 >$ymin){ $y-=int($h/2) } # y center - elsif ($y+$h > $ymax) { $y-=$h; $y=$ymin if $y<$ymin } # display above the widget - #else { $y+=0; } # display below the widget - return $x,$y; -} -=cut - - package Layout::Embedded; use base 'Gtk2::Frame'; our @ISA; diff --git a/layouts b/layouts index 14dac845..602d4086 100644 --- a/layouts +++ b/layouts @@ -334,7 +334,7 @@ HBAA = _AABox1(aa=artist) _AABox0(aa=album) [default fullscreen] Type=F -Default = Window(fullscreen=1,sticky=0) +Window = fullscreen=1,sticky=0 HBPics = _ArtistPic(maxsize=0) _Cover(maxsize=0) HBButtons1 = Prev(size=dialog) Next(size=dialog) HBButtons2 = Stop(size=dialog) Play(size=dialog) @@ -383,7 +383,7 @@ VolumeScroll = VBplayer [Fullscreen simple] Type=F -Default = Window(fullscreen=1,sticky=0) +Window = fullscreen=1,sticky=0 HBPics = _ArtistPic(maxsize=0) _Cover(maxsize=0) HBButtons1 = Prev(size=dialog) Next(size=dialog) HBButtons2 = Stop(size=dialog) Play(size=dialog)