@@ -22,6 +22,8 @@ pub enum FileRowSelection {
2222 Folder ,
2323}
2424mod imp {
25+ use crate :: dialogs:: create_distrobox_helpers:: split_repo_tag_digest;
26+
2527 use super :: * ;
2628
2729 #[ derive( Default , Properties ) ]
@@ -37,6 +39,7 @@ mod imp {
3739 pub image_row : adw:: ActionRow ,
3840 pub images_model : gtk:: StringList ,
3941 pub selected_image : RefCell < String > ,
42+ pub prefill_generation : std:: cell:: Cell < u64 > ,
4043 pub home_row_expander : adw:: ExpanderRow ,
4144 #[ property( get, set, nullable) ]
4245 pub home_folder : RefCell < Option < String > > ,
@@ -154,7 +157,22 @@ mod imp {
154157 #[ weak]
155158 obj,
156159 move |_| {
157- let picker = obj. build_image_picker_view( ) ;
160+ // Read the current subtitle and derive an initial search string
161+ let subtitle: String = obj. imp( ) . image_row. property( "subtitle" ) ;
162+ let default_sub = gettext( "Select an image..." ) ;
163+
164+ let initial_search_repo: & str = split_repo_tag_digest(
165+ if subtitle == default_sub {
166+ ""
167+ } else {
168+ & subtitle
169+ } ,
170+ ) . 0 ;
171+ // A repo is docker.io/library/xyz by default, we only want to search by 'xyz'
172+ let initial_search = initial_search_repo. rsplit( '/' ) . next( ) . unwrap_or( initial_search_repo) ;
173+
174+
175+ let picker = obj. build_image_picker_view( Some ( initial_search) ) ;
158176 obj. imp( ) . navigation_view. push( & picker) ;
159177 }
160178 ) ) ;
@@ -229,6 +247,54 @@ mod imp {
229247
230248 self . content . append ( & create_btn) ;
231249
250+ // Prefill wiring: debounce name changes to suggest an image when user hasn't interacted
251+ let obj_for_prefill = self . obj ( ) . clone ( ) ;
252+ let name_row = obj_for_prefill. imp ( ) . name_row . clone ( ) ;
253+ name_row. connect_changed ( clone ! (
254+ #[ weak]
255+ obj_for_prefill,
256+ move |entry| {
257+ let imp = obj_for_prefill. imp( ) ;
258+ let prefill_gen = imp. prefill_generation. get( ) . wrapping_add( 1 ) ;
259+ imp. prefill_generation. set( prefill_gen) ;
260+ let text = entry. text( ) . to_string( ) ;
261+ let obj_inner = obj_for_prefill. clone( ) ;
262+ glib:: MainContext :: ref_thread_default( ) . spawn_local( clone!(
263+ #[ weak]
264+ obj_inner,
265+ async move {
266+ glib:: timeout_future( std:: time:: Duration :: from_millis( 300 ) ) . await ;
267+ let imp = obj_inner. imp( ) ;
268+ if imp. prefill_generation. get( ) != prefill_gen {
269+ return ;
270+ }
271+ // don't prefill if cloning from a source
272+ if imp. clone_src. borrow( ) . is_some( ) {
273+ return ;
274+ }
275+ if text. is_empty( ) {
276+ if imp. selected_image. borrow( ) . is_empty( ) {
277+ imp. image_row. set_subtitle( & gettext( "Select an image..." ) ) ;
278+ }
279+ } else {
280+ let candidates = imp. images_model. snapshot( ) . into_iter( ) . filter_map( |item| {
281+ item. downcast:: <gtk:: StringObject >( ) . ok( ) . map( |sobj| sobj. string( ) . to_string( ) )
282+ } ) . collect:: <Vec <_>>( ) ;
283+
284+ let ( _filter, suggested_opt) =
285+ crate :: dialogs:: create_distrobox_helpers:: derive_image_prefill( & text, Some ( & candidates) ) ;
286+ if let Some ( suggested) = suggested_opt {
287+ // set subtitle as tentative prefill (do not overwrite confirmed selection)
288+ if imp. selected_image. borrow( ) . is_empty( ) {
289+ imp. image_row. set_subtitle( & suggested) ;
290+ }
291+ }
292+ }
293+ }
294+ ) ) ;
295+ }
296+ ) ) ;
297+
232298 // Create page for assemble from file
233299 let assemble_page = gtk:: Box :: new ( gtk:: Orientation :: Vertical , 12 ) ;
234300 assemble_page. set_margin_start ( 12 ) ;
@@ -486,7 +552,7 @@ impl CreateDistroboxDialog {
486552 row
487553 }
488554
489- pub fn build_image_picker_view ( & self ) -> adw:: NavigationPage {
555+ pub fn build_image_picker_view ( & self , initial_search : Option < & str > ) -> adw:: NavigationPage {
490556 let view = adw:: ToolbarView :: new ( ) ;
491557
492558 let header = adw:: HeaderBar :: new ( ) ;
@@ -495,6 +561,9 @@ impl CreateDistroboxDialog {
495561 let search_entry = gtk:: SearchEntry :: new ( ) ;
496562 search_entry. set_placeholder_text ( Some ( & gettext ( "Search image..." ) ) ) ;
497563 search_entry. set_hexpand ( true ) ;
564+ if let Some ( text) = initial_search {
565+ search_entry. set_text ( text) ;
566+ }
498567
499568 header. set_title_widget ( Some ( & search_entry) ) ;
500569
@@ -536,6 +605,9 @@ impl CreateDistroboxDialog {
536605 let child: & ImageRowItem = child. and_downcast_ref ( ) . unwrap ( ) ;
537606 child. set_image ( & image) ;
538607
608+ // TODO: Consider doing an availability check (remote / lazy-download)
609+ // to determine if an image is actually accessible, not just in the
610+ // downloaded tags set.
539611 let is_downloaded = obj. imp ( ) . downloaded_tags . borrow ( ) . contains ( image. as_str ( ) ) ;
540612 child. set_is_downloaded ( is_downloaded) ;
541613 } ) ;
@@ -673,7 +745,21 @@ impl CreateDistroboxDialog {
673745
674746 pub async fn extract_create_args ( & self ) -> Result < CreateArgs , Error > {
675747 let imp = self . imp ( ) ;
676- let image = imp. selected_image . borrow ( ) . clone ( ) ;
748+ let image = {
749+ let sel = imp. selected_image . borrow ( ) ;
750+ if sel. is_empty ( ) {
751+ // fallback to the action row subtitle (tentative prefill)
752+ let subtitle: String = imp. image_row . property ( "subtitle" ) ;
753+ let default_sub = gettext ( "Select an image..." ) ;
754+ if subtitle. is_empty ( ) || subtitle == default_sub {
755+ String :: new ( )
756+ } else {
757+ subtitle
758+ }
759+ } else {
760+ sel. clone ( )
761+ }
762+ } ;
677763 if image. is_empty ( ) && imp. clone_src . borrow ( ) . is_none ( ) {
678764 return Err ( Error :: InvalidField (
679765 "image" . into ( ) ,
0 commit comments