diff --git a/README.md b/README.md index 0570a98f7..dcc2d2396 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ emerge --ask --oneshot media-libs/freetype x11-libs/pango ``` ## How to Contribute -Join the main discussion here: +Join the main discussion here: https://devuanusers.com/thread-gtk2-revival-thread--80 Test apps, report issues, submit patches! diff --git a/configure.ac b/configure.ac index 29297423f..048454a70 100644 --- a/configure.ac +++ b/configure.ac @@ -469,7 +469,7 @@ if test "x$enable_rebuilds" = "xyes" && \ fi AC_SUBST(REBUILD) -AC_CHECK_FUNCS(lstat mkstemp flockfile getc_unlocked) +AC_CHECK_FUNCS(lstat mkstemp flockfile getc_unlocked _lock_file _unlock_file _getc_nolock) AC_CHECK_FUNCS(localtime_r) # _NL_TIME_FIRST_WEEKDAY is an enum and not a define @@ -718,6 +718,48 @@ fi saved_cflags="$CFLAGS" saved_ldflags="$LDFLAGS" +# Help GNU ld find indirect shared-library dependencies from custom +# library prefixes. This is useful for musl/cross/custom-prefix builds +# where a directly linked .so is found through -L, but its DT_NEEDED +# libraries from the same prefix are not in the default linker search path. +case "$host_os" in + linux*) + GTK_RPATH_LINK_FLAGS= + gtk_next_is_libdir=no + + for gtk_ldflag in $LDFLAGS; do + if test "x$gtk_next_is_libdir" = xyes; then + gtk_libdir="$gtk_ldflag" + gtk_next_is_libdir=no + else + case $gtk_ldflag in + -L) + gtk_next_is_libdir=yes + continue + ;; + -L*) + gtk_libdir=`echo "$gtk_ldflag" | sed 's/^-L//'` + ;; + *) + continue + ;; + esac + fi + + case " $GTK_RPATH_LINK_FLAGS " in + *" -Wl,-rpath-link,$gtk_libdir "*) + ;; + *) + GTK_RPATH_LINK_FLAGS="$GTK_RPATH_LINK_FLAGS -Wl,-rpath-link,$gtk_libdir" + ;; + esac + done + + LDFLAGS="$LDFLAGS $GTK_RPATH_LINK_FLAGS" + ;; +esac + + # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. @@ -1392,7 +1434,7 @@ CFLAGS="$CFLAGS $PANGO_CFLAGS" if $PKG_CONFIG --uninstalled $PANGO_PACKAGES; then : else - gtk_save_LIBS="$LIBS" + gtk_save_LIBS="$LIBS" LIBS="$PANGO_LIBS $LIBS" AC_LINK_IFELSE( [AC_LANG_CALL([], [pango_context_new])], @@ -1470,7 +1512,7 @@ fi GTK_EXTRA_LIBS= GTK_EXTRA_CFLAGS= GTK_DEP_LIBS="$GDK_EXTRA_LIBS $GTK_DEP_LIBS_FOR_X `$PKG_CONFIG --libs $PANGO_PACKAGES $GTK_PACKAGES_FOR_X $GTK_PACKAGES` $GTK_EXTRA_LIBS $MATH_LIB" -GTK_DEP_CFLAGS="`$PKG_CONFIG --cflags gthread-2.0 $GDK_PACKAGES $GTK_PACKAGES` $GDK_EXTRA_CFLAGS $GTK_EXTRA_CFLAGS" +GTK_DEP_CFLAGS="`$PKG_CONFIG --cflags gthread-2.0 $PANGO_PACKAGES $GDK_PACKAGES $GTK_PACKAGES` $GDK_EXTRA_CFLAGS $GTK_EXTRA_CFLAGS" if test x"$os_win32" = xyes; then GTK_EXTRA_CFLAGS="$msnative_struct" @@ -1594,6 +1636,7 @@ else gtk_save_libs="$LIBS" LIBS="$CUPS_LIBS" AC_CHECK_FUNCS(httpGetAuthString) + AC_CHECK_FUNCS(httpConnect2 httpReconnect2) LIBS="$gtk_save_libs" fi fi diff --git a/demos/gtk-demo/main.c b/demos/gtk-demo/main.c index a26e8adcf..529e3ea37 100644 --- a/demos/gtk-demo/main.c +++ b/demos/gtk-demo/main.c @@ -992,11 +992,11 @@ main (int argc, char **argv) NULL); gtk_text_buffer_create_tag (source_buffer, "preprocessor", "style", PANGO_STYLE_OBLIQUE, - "foreground", "burlywood4", + "foreground", "burlywood4", NULL); gtk_text_buffer_create_tag (source_buffer, "function", "weight", PANGO_WEIGHT_BOLD, - "foreground", "DarkGoldenrod4", + "foreground", "DarkGoldenrod4", NULL); g_object_unref (source_buffer); diff --git a/docs/reference/gdk/Makefile.am b/docs/reference/gdk/Makefile.am index c0facc151..fe5d9742a 100644 --- a/docs/reference/gdk/Makefile.am +++ b/docs/reference/gdk/Makefile.am @@ -37,7 +37,7 @@ IGNORE_HFILES= \ # Extra files to add when scanning (relative to $srcdir) EXTRA_HFILES= \ - ../../../gdk/x11/gdkx.h + $(top_srcdir)/gdk/x11/gdkx.h # CFLAGS and LDFLAGS for compiling scan program. Only needed # if $(DOC_MODULE).types is non-empty. @@ -159,4 +159,3 @@ EXTRA_DIST += version.xml.in # working when the top-level test target descends into the documentation # reference directories. test: - diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am index fcd7e9173..05500507e 100644 --- a/docs/reference/gtk/Makefile.am +++ b/docs/reference/gtk/Makefile.am @@ -391,4 +391,3 @@ dist-hook-local: $(BUILT_EXTRA_DIST) # working when the top-level test target descends into the documentation # reference directories. test: - diff --git a/docs/reference/libgail-util/Makefile.am b/docs/reference/libgail-util/Makefile.am index b57a2e788..887c4a7b4 100644 --- a/docs/reference/libgail-util/Makefile.am +++ b/docs/reference/libgail-util/Makefile.am @@ -39,4 +39,3 @@ include $(top_srcdir)/gtk-doc.make # working when the top-level test target descends into the documentation # reference directories. test: - diff --git a/gdk/Makefile.am b/gdk/Makefile.am index 1b186afa1..f9804aa85 100644 --- a/gdk/Makefile.am +++ b/gdk/Makefile.am @@ -2,6 +2,8 @@ include $(top_srcdir)/Makefile.decl -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ENV = \ + CC="$(CC)" INTROSPECTION_SCANNER_ARGS = \ --warn-all \ --add-include-path=../gdk @@ -213,6 +215,11 @@ Gdk_2_0_gir_CFLAGS = \ -I$(top_builddir) \ -I$(top_builddir)/gdk \ $(INCLUDES) + +if OS_WIN32 +Gdk_2_0_gir_CFLAGS += -I$(srcdir)/win32 -I$(srcdir) +endif + INTROSPECTION_GIRS += Gdk-2.0.gir if USE_X11 @@ -268,6 +275,63 @@ introspection_files += $(filter-out x11/gdkx.h, $(x11_introspection_files)) endif # USE_X11 +if OS_WIN32 +win32_introspection_files = \ + win32/gdkapplaunchcontext-win32.c \ + win32/gdkcolor-win32.c \ + win32/gdkcursor-win32.c \ + win32/gdkdisplay-win32.c \ + win32/gdkdnd-win32.c \ + win32/gdkdrawable-win32.c \ + win32/gdkdrawable-win32.h \ + win32/gdkevents-win32.c \ + win32/gdkfont-win32.c \ + win32/gdkgc-win32.c \ + win32/gdkgeometry-win32.c \ + win32/gdkglobals-win32.c \ + win32/gdkimage-win32.c \ + win32/gdkim-win32.c \ + win32/gdkinput.c \ + win32/gdkinput-win32.c \ + win32/gdkinput-win32.h \ + win32/gdkkeys-win32.c \ + win32/gdkmain-win32.c \ + win32/gdkpixmap-win32.c \ + win32/gdkpixmap-win32.h \ + win32/gdkprivate-win32.h \ + win32/gdkproperty-win32.c \ + win32/gdkscreen-win32.c \ + win32/gdkselection-win32.c \ + win32/gdkspawn-win32.c \ + win32/gdktestutils-win32.c \ + win32/gdkvisual-win32.c \ + win32/gdkwin32.h \ + win32/gdkwin32id.c \ + win32/gdkwin32keys.h \ + win32/gdkwindow-win32.c \ + win32/gdkwindow-win32.h + +GdkWin32-2.0.gir: $(gdktargetlib) Gdk-2.0.gir Makefile +GdkWin32_2_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Gdk --symbol-prefix=gdk --include-uninstalled=Gdk-2.0.gir +GdkWin32_2_0_gir_INCLUDES = Gio-2.0 Gdk-2.0 GdkPixbuf-2.0 Pango-1.0 +GdkWin32_2_0_gir_LIBS = $(gdktargetlib) +GdkWin32_2_0_gir_FILES = $(win32_introspection_files) +GdkWin32_2_0_gir_EXPORT_PACKAGES = gdk-win32-2.0 +GdkWin32_2_0_gir_CFLAGS = \ + -DGDK_COMPILATION \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_builddir)/gdk \ + -I$(srcdir)/win32 \ + -I$(srcdir) \ + $(INCLUDES) \ + -L$(top_builddir)/gdk +INTROSPECTION_GIRS += GdkWin32-2.0.gir + +introspection_files += $(filter-out win32/gdkwin32.h, $(win32_introspection_files)) + +endif # OS_WIN32 + girdir = $(datadir)/gir-1.0 gir_DATA = $(INTROSPECTION_GIRS) diff --git a/gdk/gdkrgb.c b/gdk/gdkrgb.c index b0ff66833..1e37a6181 100644 --- a/gdk/gdkrgb.c +++ b/gdk/gdkrgb.c @@ -2242,6 +2242,51 @@ gdk_rgb_convert_8880_br (GdkRgbInfo *image_info, GdkImage *image, } } +static void +gdk_rgb_convert_0AAA_br (GdkRgbInfo *image_info, GdkImage *image, + gint x0, gint y0, gint width, gint height, + const guchar *buf, int rowstride, + gint x_align, gint y_align, GdkRgbCmap *cmap) +{ + int y, w; + guchar *obuf, *p; + gint bpl; + const guchar *bptr, *bp2; + guchar r, g, b; + + bptr = buf; + bpl = image->bpl; + obuf = ((guchar *) image->mem) + y0 * bpl + x0 * 4; + + for (y = 0; y < height; y++) + { + bp2 = bptr; + p = obuf; + w = width; + + while (w--) + { + r = *bp2++; + g = *bp2++; + b = *bp2++; + + /* Since the R, G and B channels are coded in a char, we + * replicate the two most significant bits to the two least + * significant ones. + */ + *((guint32 *) p) = 0x3 << 30 | + b << 22 | (b >> 6) << 20 | + g << 12 | (g >> 6) << 10 | + r << 2 | (r >> 6); + + p += 4; + } + + bptr += rowstride; + obuf += bpl; + } +} + /* Generic truecolor/directcolor conversion function. Slow, but these are oddball modes. */ static void @@ -3195,6 +3240,11 @@ gdk_rgb_select_conv (GdkRgbInfo *image_info) #endif } #endif + else if (bpp == 32 && (depth == 30 || depth == 32) && + vtype == GDK_VISUAL_TRUE_COLOR && + (red_mask == 0x3ff && green_mask == 0xffc00 && + blue_mask == 0x3ff00000 && byte_order == GDK_LSB_FIRST)) + conv = gdk_rgb_convert_0AAA_br; else if (vtype == GDK_VISUAL_TRUE_COLOR && byte_order == GDK_LSB_FIRST) { conv = gdk_rgb_convert_truecolor_lsb; diff --git a/gdk/makegdkalias.pl b/gdk/makegdkalias.pl index 97a6e6007..27646cc0a 100755 --- a/gdk/makegdkalias.pl +++ b/gdk/makegdkalias.pl @@ -14,7 +14,7 @@ print < -#ifdef G_HAVE_GNUC_VISIBILITY +#if defined(G_HAVE_GNUC_VISIBILITY) && !defined(__APPLE__) EOF diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index 368e040ec..bad737da2 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -599,6 +599,25 @@ gdk_screen_get_rgba_colormap (_gdk_screen); } +-(void)viewWillDraw +{ + /* macOS 11 (Big Sur) and newer may use a higher precision backing + * format for layer contents. For GTK2's 8-bit drawing this can add + * avoidable conversion overhead. Use a dynamic Objective-C call here + * instead of a hard QuartzCore/CALayer dependency, so old SDKs and old + * macOS targets keep building and keep their old behaviour. + */ + if (gdk_quartz_osx_version () >= GDK_OSX_BIG_SUR) + { + id layer = [self layer]; + + if (layer && [layer respondsToSelector: @selector(setContentsFormat:)]) + [layer setValue: @"RGBA8Uint" forKey: @"contentsFormat"]; + } + + [super viewWillDraw]; +} + -(void)drawRect: (NSRect)rect { GdkRectangle gdk_rect; diff --git a/gdk/quartz/gdkdrawable-quartz.c b/gdk/quartz/gdkdrawable-quartz.c index 11dd12737..eb520f08d 100644 --- a/gdk/quartz/gdkdrawable-quartz.c +++ b/gdk/quartz/gdkdrawable-quartz.c @@ -893,7 +893,8 @@ gdk_quartz_drawable_release_context (GdkDrawable *drawable, if (window_impl->in_paint_rect_count == 0) { _gdk_quartz_drawable_flush (drawable); - [window_impl->view unlockFocus]; + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + [window_impl->view unlockFocus]; } } else if (GDK_IS_PIXMAP_IMPL_QUARTZ (drawable)) diff --git a/gdk/quartz/gdkglobals-quartz.c b/gdk/quartz/gdkglobals-quartz.c index f013f2830..4e596eef5 100644 --- a/gdk/quartz/gdkglobals-quartz.c +++ b/gdk/quartz/gdkglobals-quartz.c @@ -34,9 +34,20 @@ gdk_quartz_osx_version (void) if (minor == GDK_OSX_UNSUPPORTED) { - OSErr err = Gestalt (gestaltSystemVersionMinor, (SInt32*)&minor); + gint32 major = GDK_OSX_UNSUPPORTED; + OSErr err = Gestalt (gestaltSystemVersionMajor, (SInt32*)&major); g_return_val_if_fail (err == noErr, GDK_OSX_UNSUPPORTED); + + err = Gestalt (gestaltSystemVersionMinor, (SInt32*)&minor); + g_return_val_if_fail (err == noErr, GDK_OSX_UNSUPPORTED); + + /* GTK2 encodes macOS 10.x releases by their minor version. + * Map macOS 11+ into the same sequence: + * 11 -> 16, 12 -> 17, 13 -> 18, ... + */ + if (major > 10) + minor = major + 5; } if (minor < GDK_OSX_MIN) diff --git a/gdk/quartz/gdkquartz.h b/gdk/quartz/gdkquartz.h index 8206db019..42aac50b9 100644 --- a/gdk/quartz/gdkquartz.h +++ b/gdk/quartz/gdkquartz.h @@ -54,7 +54,13 @@ typedef enum GDK_OSX_SIERRA = 12, GDK_OSX_HIGH_SIERRA = 13, GDK_OSX_MOJAVE = 14, - GDK_OSX_CURRENT = 14, + GDK_OSX_CATALINA = 15, + GDK_OSX_BIG_SUR = 16, + GDK_OSX_MONTEREY = 17, + GDK_OSX_VENTURA = 18, + GDK_OSX_SONOMA = 19, + GDK_OSX_SEQUOIA = 20, + GDK_OSX_CURRENT = 20, GDK_OSX_NEW = 99 } GdkOSXVersion; diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index 6c7a2facb..38ada80b2 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -104,7 +104,8 @@ gdk_window_impl_quartz_get_context (GdkDrawable *drawable, * and for widgets that send fake expose events like the arrow * buttons in spinbuttons or the position marker in rulers. */ - if (window_impl->in_paint_rect_count == 0) + if (window_impl->in_paint_rect_count == 0 && + gdk_quartz_osx_version () < GDK_OSX_MOJAVE) { if (![window_impl->view lockFocusIfCanDraw]) return NULL; @@ -468,7 +469,8 @@ _gdk_windowing_before_process_all_updates (void) { in_process_all_updates = TRUE; - NSDisableScreenUpdates (); + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + NSDisableScreenUpdates (); } void @@ -489,8 +491,9 @@ _gdk_windowing_after_process_all_updates (void) /* 10.14 needs to be told that the view needs to be redrawn, see * https://gitlab.gnome.org/GNOME/gtk/issues/1479 */ - if (gdk_quartz_osx_version() >= GDK_OSX_MOJAVE) - [[nswindow contentView] setNeedsDisplay:YES]; + if (gdk_quartz_osx_version () >= GDK_OSX_MOJAVE && + gdk_quartz_osx_version () < GDK_OSX_BIG_SUR) + [[nswindow contentView] setNeedsDisplay:YES]; [nswindow enableFlushWindow]; [nswindow flushWindow]; [nswindow release]; @@ -502,7 +505,8 @@ _gdk_windowing_after_process_all_updates (void) in_process_all_updates = FALSE; - NSEnableScreenUpdates (); + if (gdk_quartz_osx_version () < GDK_OSX_MOJAVE) + NSEnableScreenUpdates (); } static void diff --git a/gdk/win32/gdkkeys-win32.c b/gdk/win32/gdkkeys-win32.c index d8ab30347..9a34083c4 100644 --- a/gdk/win32/gdkkeys-win32.c +++ b/gdk/win32/gdkkeys-win32.c @@ -692,6 +692,7 @@ update_keymap (GdkKeymap *gdk_keymap) if (hkls_len != keymap->layout_handles->len) keymap->keysym_tab = g_renew (guint, keymap->keysym_tab, keysym_tab_size); + memset (key_state, 0, sizeof(key_state)); memset (keymap->keysym_tab, 0, keysym_tab_size); g_array_set_size (keymap->layout_handles, hkls_len); g_array_set_size (keymap->options, hkls_len); diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c index b7ea0404a..1a21a1b67 100644 --- a/gdk/win32/gdkwindow-win32.c +++ b/gdk/win32/gdkwindow-win32.c @@ -2767,6 +2767,10 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, * WindowFromPoint() can find our windows, we follow similar logic * here, and ignore invisible and disabled windows. */ + UINT cwp_flags = CWP_SKIPDISABLED | + CWP_SKIPINVISIBLE | + CWP_SKIPTRANSPARENT; + hwnd = GetDesktopWindow (); do { window = gdk_win32_handle_table_lookup ((GdkNativeWindow) hwnd); @@ -2777,8 +2781,7 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, break; screen_to_client (hwnd, screen_pt, &client_pt); - hwndc = ChildWindowFromPointEx (hwnd, client_pt, CWP_SKIPDISABLED | - CWP_SKIPINVISIBLE); + hwndc = ChildWindowFromPointEx (hwnd, client_pt, cwp_flags); /* Verify that we're really inside the client area of the window */ if (hwndc != hwnd) @@ -2789,6 +2792,8 @@ _gdk_windowing_window_at_pointer (GdkDisplay *display, hwndc = hwnd; } + /* Only ignore top-level transparent windows */ + cwp_flags &= ~CWP_SKIPTRANSPARENT; } while (hwndc != hwnd && (hwnd = hwndc, 1)); } diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 5cbe2a326..083fd07b7 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -2,6 +2,9 @@ include $(top_srcdir)/Makefile.decl -include $(INTROSPECTION_MAKEFILE) INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ENV = \ + CC="$(CC)" \ + PATH="$(builddir):$(builddir)/.libs:$${PATH}:" INTROSPECTION_SCANNER_ARGS = \ --warn-all \ --add-include-path=../gdk @@ -1139,7 +1142,7 @@ $(GTK_UPDATE_ICON_CACHE_MANIFEST): echo '' ) >$@ $(GTK_UPDATE_ICON_CACHE_RC): - (echo -e '#include \nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST '$(GTK_UPDATE_ICON_CACHE_MANIFEST)) >$@ + (echo -e '#include \nCREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "'$(GTK_UPDATE_ICON_CACHE_MANIFEST)'"') >$@ $(GTK_UPDATE_ICON_CACHE_MANIFEST_OBJECT): $(GTK_UPDATE_ICON_CACHE_RC) $(GTK_UPDATE_ICON_CACHE_MANIFEST) $(WINDRES) --input $< --output $@ --output-format=coff diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c index 19dc0196f..f50da2493 100644 --- a/gtk/gtkcalendar.c +++ b/gtk/gtkcalendar.c @@ -688,8 +688,7 @@ gtk_calendar_init (GtkCalendar *calendar) #else static const char *month_format = NULL; char buffer[255]; - GDateTime *tmp_dt; - gchar *tmp_str; + time_t tmp_time; #endif GtkCalendarPrivate *priv; gchar *year_before; @@ -710,15 +709,9 @@ gtk_calendar_init (GtkCalendar *calendar) for (i=0; i<7; i++) { #ifndef G_OS_WIN32 - tmp_dt = g_date_time_new_utc (2001, 1, i + 1, 12, 0, 0.0); - if (tmp_dt) - { - tmp_str = g_date_time_format (tmp_dt, "%a"); - default_abbreviated_dayname[i] = tmp_str ? tmp_str : g_strdup (""); - g_date_time_unref (tmp_dt); - } - else - default_abbreviated_dayname[i] = g_strdup (""); + tmp_time = (i + 3) * 86400; + strftime (buffer, sizeof (buffer), "%a", gmtime (&tmp_time)); + default_abbreviated_dayname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL); #else if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SABBREVDAYNAME1 + (i+6)%7, wbuffer, G_N_ELEMENTS (wbuffer))) @@ -732,30 +725,21 @@ gtk_calendar_init (GtkCalendar *calendar) for (i=0; i<12; i++) { #ifndef G_OS_WIN32 - tmp_dt = g_date_time_new_utc (2001, i + 1, 1, 12, 0, 0.0); + tmp_time = i * 2764800; if (G_UNLIKELY (month_format == NULL)) { buffer[0] = '\0'; month_format = "%OB"; - tmp_str = tmp_dt ? g_date_time_format (tmp_dt, month_format) : NULL; - if (tmp_str) - { - g_strlcpy (buffer, tmp_str, sizeof (buffer)); - g_free (tmp_str); - } + strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time)); + /* "%OB" is not supported in Linux with glibc < 2.27 */ if (!strcmp (buffer, "%OB") || !strcmp (buffer, "OB") || !strcmp (buffer, "")) month_format = "%B"; } - - if (tmp_dt) - { - tmp_str = g_date_time_format (tmp_dt, month_format); - default_monthname[i] = tmp_str ? tmp_str : g_strdup (""); - g_date_time_unref (tmp_dt); - } else - default_monthname[i] = g_strdup (""); + strftime (buffer, sizeof (buffer), month_format, gmtime (&tmp_time)); + + default_monthname[i] = g_locale_to_utf8 (buffer, -1, NULL, NULL, NULL); #else if (!GetLocaleInfoW (GetThreadLocale (), LOCALE_SMONTHNAME1 + i, wbuffer, G_N_ELEMENTS (wbuffer))) diff --git a/gtk/gtkclipboard-quartz.c b/gtk/gtkclipboard-quartz.c index b8bc78fb0..f6a1f5aa5 100644 --- a/gtk/gtkclipboard-quartz.c +++ b/gtk/gtkclipboard-quartz.c @@ -884,11 +884,11 @@ gtk_clipboard_wait_for_targets (GtkClipboard *clipboard, clipboard->n_cached_targets != -1) { if (n_targets) - *n_targets = clipboard->n_cached_targets; + *n_targets = clipboard->n_cached_targets; if (targets) - *targets = _gtk_memdup2 (clipboard->cached_targets, - clipboard->n_cached_targets * sizeof (GdkAtom)); + *targets = _gtk_memdup2 (clipboard->cached_targets, + clipboard->n_cached_targets * sizeof (GdkAtom)); return TRUE; } @@ -909,11 +909,11 @@ gtk_clipboard_wait_for_targets (GtkClipboard *clipboard, result = gtk_selection_data_get_targets (data, &tmp_targets, &tmp_n_targets); if (gdk_display_supports_selection_notification (gtk_clipboard_get_display (clipboard))) - { - clipboard->n_cached_targets = tmp_n_targets; - clipboard->cached_targets = _gtk_memdup2 (tmp_targets, - tmp_n_targets * sizeof (GdkAtom)); - } + { + clipboard->n_cached_targets = tmp_n_targets; + clipboard->cached_targets = _gtk_memdup2 (tmp_targets, + tmp_n_targets * sizeof (GdkAtom)); + } if (n_targets) *n_targets = tmp_n_targets; diff --git a/gtk/gtkclipboard.c b/gtk/gtkclipboard.c index 132d21f62..e9e913145 100644 --- a/gtk/gtkclipboard.c +++ b/gtk/gtkclipboard.c @@ -548,7 +548,7 @@ gtk_clipboard_set_contents (GtkClipboard *clipboard, clipboard_add_owner_notify (clipboard); } - } + } clipboard->get_func = get_func; clipboard->clear_func = clear_func; @@ -1758,11 +1758,11 @@ gtk_clipboard_wait_for_targets (GtkClipboard *clipboard, clipboard->n_cached_targets != -1) { if (n_targets) - *n_targets = clipboard->n_cached_targets; + *n_targets = clipboard->n_cached_targets; if (targets) - *targets = _gtk_memdup2 (clipboard->cached_targets, - clipboard->n_cached_targets * sizeof (GdkAtom)); + *targets = _gtk_memdup2 (clipboard->cached_targets, + clipboard->n_cached_targets * sizeof (GdkAtom)); return TRUE; } @@ -1783,11 +1783,11 @@ gtk_clipboard_wait_for_targets (GtkClipboard *clipboard, result = gtk_selection_data_get_targets (data, &tmp_targets, &tmp_n_targets); if (gdk_display_supports_selection_notification (gtk_clipboard_get_display (clipboard))) - { - clipboard->n_cached_targets = tmp_n_targets; - clipboard->cached_targets = _gtk_memdup2 (tmp_targets, - tmp_n_targets * sizeof (GdkAtom)); - } + { + clipboard->n_cached_targets = tmp_n_targets; + clipboard->cached_targets = _gtk_memdup2 (tmp_targets, + tmp_n_targets * sizeof (GdkAtom)); + } if (n_targets) *n_targets = tmp_n_targets; diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c index 2f7007fd2..e4c98c8c2 100644 --- a/gtk/gtkcontainer.c +++ b/gtk/gtkcontainer.c @@ -40,6 +40,9 @@ #include "gtktoolbar.h" #include #include +#ifdef GDK_WINDOWING_QUARTZ +#include "gdk/quartz/gdkquartz.h" +#endif #include "gtkalias.h" @@ -1357,7 +1360,16 @@ gtk_container_idle_sizer (gpointer data) gtk_container_check_resize (GTK_CONTAINER (widget)); } +#ifdef GDK_WINDOWING_QUARTZ + /* macOS 11 and newer can trigger expensive full-window redraws here. + * Keep the traditional update processing path for older Quartz targets, + * but let the normal GDK update idle handle updates on Big Sur and newer. + */ + if (gdk_quartz_osx_version () < GDK_OSX_BIG_SUR) + gdk_window_process_all_updates (); +#else gdk_window_process_all_updates (); +#endif return FALSE; } diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 2ce2d6c8f..ec6e86843 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -20,6 +20,7 @@ */ #include "config.h" +#include #include "gdk/gdkkeysyms.h" #include "gtkalignment.h" @@ -140,14 +141,12 @@ _gtk_file_chooser_profile_log (const char *func, int indent, const char *msg1, c #endif - typedef struct _GtkFileChooserDefaultClass GtkFileChooserDefaultClass; #define GTK_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) #define GTK_IS_FILE_CHOOSER_DEFAULT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_DEFAULT)) #define GTK_FILE_CHOOSER_DEFAULT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_DEFAULT, GtkFileChooserDefaultClass)) -#define MAX_LOADING_TIME 500 #define DEFAULT_NEW_FOLDER_NAME _("Type name of new folder") @@ -207,6 +206,7 @@ enum { MODEL_COL_NAME_COLLATED, MODEL_COL_IS_FOLDER, MODEL_COL_IS_SENSITIVE, + MODEL_COL_IS_HIDDEN, MODEL_COL_LIST_PIXBUF, MODEL_COL_ICON_PIXBUF, MODEL_COL_SIZE_TEXT, @@ -225,6 +225,7 @@ enum { G_TYPE_STRING, /* MODEL_COL_NAME_COLLATED */ \ G_TYPE_BOOLEAN, /* MODEL_COL_IS_FOLDER */ \ G_TYPE_BOOLEAN, /* MODEL_COL_IS_SENSITIVE */ \ + G_TYPE_BOOLEAN, /* MODEL_COL_IS_HIDDEN */ \ GDK_TYPE_PIXBUF, /* MODEL_COL_LIST_PIXBUF */ \ GDK_TYPE_PIXBUF, /* MODEL_COL_ICON_PIXBUF */ \ G_TYPE_STRING, /* MODEL_COL_SIZE_TEXT */ \ @@ -257,9 +258,19 @@ typedef enum { #define FALLBACK_ICON_VIEW_ICON_SIZE 48 #define ICON_VIEW_ITEM_WIDTH 128 +#define COMPACT_VIEW_ITEM_WIDTH 170 +#define COMPACT_VIEW_SPACING 4 +#define COMPACT_VIEW_ROW_SPACING 1 +#define COMPACT_VIEW_COLUMN_SPACING 16 +#define COMPACT_VIEW_MARGIN 2 #define ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN 128 +#define VIEW_MODE_USES_ICON_VIEW(mode) ((mode) == VIEW_MODE_ICON || (mode) == VIEW_MODE_COMPACT) + #define PREVIEW_HBOX_SPACING 12 +#define PREVIEW_PANEL_WIDTH 240 +#define PREVIEW_PANEL_IMAGE_MIN_SIZE 64 +#define PREVIEW_PANEL_IMAGE_MAX_SIZE 1024 #define NUM_LINES 45 #define NUM_CHARS 60 @@ -279,6 +290,14 @@ static void gtk_file_chooser_default_get_property (GObject *ob GValue *value, GParamSpec *pspec); static void gtk_file_chooser_default_dispose (GObject *object); +static void rename_clear_state (GtkFileChooserDefault *impl); +static void rename_selected_file (GtkFileChooserDefault *impl); +static void copy_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl); +static void cut_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl); +static void paste_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl); static void gtk_file_chooser_default_show_all (GtkWidget *widget); static void gtk_file_chooser_default_realize (GtkWidget *widget); static void gtk_file_chooser_default_map (GtkWidget *widget); @@ -406,7 +425,6 @@ static void remove_bookmark_button_clicked_cb (GtkButton *button, static void update_cell_renderer_attributes (GtkFileChooserDefault *impl); -static void load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state); static void browse_files_center_selected_row (GtkFileChooserDefault *impl); static void view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode); @@ -426,6 +444,12 @@ static void icon_view_scale_queue_cache_clear (GtkFileChooserDefault *impl); static gboolean get_visible_range (GtkTreePath **start, GtkTreePath **end, GtkFileChooserDefault *impl); +static void icon_view_visible_range_schedule (GtkFileChooserDefault *impl); +static void icon_view_visible_range_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + GtkFileChooserDefault *impl); +static void icon_view_visible_range_adjustment_changed_cb (GtkAdjustment *adjustment, + GtkFileChooserDefault *impl); static void location_button_toggled_cb (GtkToggleButton *toggle, GtkFileChooserDefault *impl); @@ -485,6 +509,7 @@ typedef struct { GtkTreeModelFilter parent; GtkFileChooserDefault *impl; + } ShortcutsPaneModelFilter; typedef struct { @@ -775,6 +800,7 @@ _gtk_file_chooser_default_init (GtkFileChooserDefault *impl) impl->use_preview_label = TRUE; impl->select_multiple = FALSE; impl->show_hidden = FALSE; + impl->show_thumbnails = TRUE; impl->show_size_column = TRUE; impl->list_view_icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; impl->icon_view_icon_size = FALLBACK_ICON_VIEW_ICON_SIZE; @@ -871,14 +897,979 @@ pending_select_files_add (GtkFileChooserDefault *impl, g_slist_prepend (impl->pending_select_files, g_object_ref (file)); } + +static void +thumbnail_standard_icon_cache_clear (GtkFileChooserDefault *impl) +{ + if (impl->thumbnail_standard_icon_pixbuf) + { + g_object_unref (impl->thumbnail_standard_icon_pixbuf); + impl->thumbnail_standard_icon_pixbuf = NULL; + } + + impl->thumbnail_standard_icon_size = 0; +} + +static GdkPixbuf * +thumbnail_standard_icon_cache_get (GtkFileChooserDefault *impl, + GFileInfo *info, + gint icon_size) +{ + GFileInfo *icon_info; + GdkPixbuf *pixbuf; + + if (impl->thumbnail_standard_icon_pixbuf && + impl->thumbnail_standard_icon_size == icon_size) + return g_object_ref (impl->thumbnail_standard_icon_pixbuf); + + thumbnail_standard_icon_cache_clear (impl); + + icon_info = g_file_info_dup (info); + g_file_info_remove_attribute (icon_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + + pixbuf = _gtk_file_info_render_icon (icon_info, GTK_WIDGET (impl), icon_size); + g_object_unref (icon_info); + + if (pixbuf == NULL) + return NULL; + + impl->thumbnail_standard_icon_pixbuf = g_object_ref (pixbuf); + impl->thumbnail_standard_icon_size = icon_size; + + return pixbuf; +} + +static void +thumbnail_queue_clear (GtkFileChooserDefault *impl) +{ + if (impl->thumbnail_queue_idle_id) + { + g_source_remove (impl->thumbnail_queue_idle_id); + impl->thumbnail_queue_idle_id = 0; + } + + if (impl->thumbnail_cancellable) + { + g_cancellable_cancel (impl->thumbnail_cancellable); + g_object_unref (impl->thumbnail_cancellable); + impl->thumbnail_cancellable = NULL; + } + + g_slist_foreach (impl->thumbnail_queue, (GFunc) g_object_unref, NULL); + g_slist_free (impl->thumbnail_queue); + impl->thumbnail_queue = NULL; + impl->thumbnail_queue_tail = NULL; + + if (impl->thumbnail_queued_files) + { + g_hash_table_destroy (impl->thumbnail_queued_files); + impl->thumbnail_queued_files = NULL; + } + + impl->thumbnail_worker_running = FALSE; + impl->thumbnail_generation_serial++; +} + +static void +thumbnail_file_list_clear_pixbuf_caches (GtkFileChooserDefault *impl) +{ + if (impl->browse_files_model) + { + _gtk_file_system_model_clear_cache (impl->browse_files_model, + MODEL_COL_LIST_PIXBUF); + _gtk_file_system_model_clear_cache (impl->browse_files_model, + MODEL_COL_ICON_PIXBUF); + } + + if (impl->search_model) + { + _gtk_file_system_model_clear_cache (impl->search_model, + MODEL_COL_LIST_PIXBUF); + _gtk_file_system_model_clear_cache (impl->search_model, + MODEL_COL_ICON_PIXBUF); + } + + if (impl->recent_model) + { + _gtk_file_system_model_clear_cache (impl->recent_model, + MODEL_COL_LIST_PIXBUF); + _gtk_file_system_model_clear_cache (impl->recent_model, + MODEL_COL_ICON_PIXBUF); + } + + if (impl->browse_files_current_view) + gtk_widget_queue_draw (impl->browse_files_current_view); +} + +static gint +thumbnail_queue_get_icon_size (GtkFileChooserDefault *impl) +{ + gint icon_size; + + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) + { + icon_size = impl->icon_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_ICON_VIEW_ICON_SIZE; + } + else + { + icon_size = impl->list_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; + } + + return MAX (1, icon_size); +} + +#define FILECHOOSER_GENERATED_THUMBNAIL "filechooser::generated-thumbnail" +#define FILECHOOSER_GENERATED_THUMBNAIL_SIZE "filechooser::generated-thumbnail-size" + +/* First conservative limit. It avoids surprising CPU/I/O spikes on older + * hardware while still covering typical camera and screenshot files. + */ +#define FILECHOOSER_THUMBNAIL_MAX_FILE_SIZE (20 * 1024 * 1024) +#define FILECHOOSER_THUMBNAIL_NORMAL_SIZE 128 +#define FILECHOOSER_THUMBNAIL_LARGE_SIZE 256 +#define FILECHOOSER_THUMBNAIL_X_LARGE_SIZE 512 +#define FILECHOOSER_THUMBNAIL_XX_LARGE_SIZE 1024 + +typedef struct +{ + GtkFileChooserDefault *impl; + GFile *file; + GCancellable *cancellable; + guint generation_serial; + gint icon_size; + GdkPixbuf *pixbuf; + gchar *uri; + gchar *thumbnail_path; + gchar *queried_thumbnail_path; + gboolean failed; +} ThumbnailJob; + + +static gboolean thumbnail_debug_enabled (void); +static void thumbnail_debug_log_file (const gchar *where, + GFile *file, + const gchar *format, + ...) G_GNUC_PRINTF (3, 4); + +static gint +thumbnail_queue_get_cache_size (GtkFileChooserDefault *impl) +{ + gint icon_size; + gint cache_size; + + icon_size = thumbnail_queue_get_icon_size (impl); + + /* Use the smallest freedesktop thumbnail cache class that can cover + * the visible icon size. The real thumbnail geometry is checked later, + * so broken undersized cache files do not get accepted just because they + * live in thumbnails/normal/. + */ + if (icon_size <= FILECHOOSER_THUMBNAIL_NORMAL_SIZE) + cache_size = FILECHOOSER_THUMBNAIL_NORMAL_SIZE; + else if (icon_size <= FILECHOOSER_THUMBNAIL_LARGE_SIZE) + cache_size = FILECHOOSER_THUMBNAIL_LARGE_SIZE; + else if (icon_size <= FILECHOOSER_THUMBNAIL_X_LARGE_SIZE) + cache_size = FILECHOOSER_THUMBNAIL_X_LARGE_SIZE; + else + cache_size = FILECHOOSER_THUMBNAIL_XX_LARGE_SIZE; + + if (thumbnail_debug_enabled ()) + g_debug ("GtkFileChooser thumbnail: cache-size: visible=%d cache=%d", + icon_size, + cache_size); + + return cache_size; +} + + +static gboolean +thumbnail_queue_should_enqueue (GtkFileChooserDefault *impl, + GFile *file, + GFileInfo *info) +{ + GObject *thumbnail; + const gchar *content_type; + goffset size; + gint cache_size; + gint thumbnail_size; + + if (!impl->show_thumbnails) + return FALSE; + + if (file == NULL || info == NULL) + return FALSE; + + cache_size = thumbnail_queue_get_cache_size (impl); + + if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)) + return FALSE; + + if (g_file_info_get_attribute_boolean (info, "filechooser::thumbnail_generation_failed")) + return FALSE; + + thumbnail_size = g_file_info_get_attribute_int32 (info, + FILECHOOSER_GENERATED_THUMBNAIL_SIZE); + thumbnail = g_file_info_get_attribute_object (info, + FILECHOOSER_GENERATED_THUMBNAIL); + if (thumbnail && thumbnail_size >= cache_size) + return FALSE; + + content_type = g_file_info_get_content_type (info); + if (content_type == NULL || !g_str_has_prefix (content_type, "image/")) + return FALSE; + + size = g_file_info_get_size (info); + if (size > FILECHOOSER_THUMBNAIL_MAX_FILE_SIZE) + return FALSE; + + /* Keep the first implementation conservative: only local files can be + * thumbnailed without surprising network or VFS latency. + */ + if (!g_file_is_native (file)) + return FALSE; + + return TRUE; +} + +static void +thumbnail_queue_ensure (GtkFileChooserDefault *impl) +{ + if (!impl->thumbnail_cancellable) + impl->thumbnail_cancellable = g_cancellable_new (); + + if (!impl->thumbnail_queued_files) + impl->thumbnail_queued_files = + g_hash_table_new_full ((GHashFunc) g_file_hash, + (GEqualFunc) g_file_equal, + g_object_unref, + NULL); +} + +static GFile * +thumbnail_queue_pop_next (GtkFileChooserDefault *impl) +{ + GFile *file; + GSList *link; + + if (impl->thumbnail_queue == NULL) + return NULL; + + link = impl->thumbnail_queue; + file = link->data; + + impl->thumbnail_queue = g_slist_delete_link (impl->thumbnail_queue, link); + + if (impl->thumbnail_queue == NULL) + impl->thumbnail_queue_tail = NULL; + + if (impl->thumbnail_queued_files) + g_hash_table_remove (impl->thumbnail_queued_files, file); + + return file; +} + +static void thumbnail_queue_schedule (GtkFileChooserDefault *impl); +static gboolean thumbnail_job_apply_cb (gpointer data); +static const gchar * +thumbnail_cache_size_dir_for_dimension (gint dimension) +{ + if (dimension <= FILECHOOSER_THUMBNAIL_NORMAL_SIZE) + return "normal"; + if (dimension <= FILECHOOSER_THUMBNAIL_LARGE_SIZE) + return "large"; + if (dimension <= FILECHOOSER_THUMBNAIL_X_LARGE_SIZE) + return "x-large"; + if (dimension <= FILECHOOSER_THUMBNAIL_XX_LARGE_SIZE) + return "xx-large"; + + return NULL; +} + +static gint +thumbnail_cache_max_dimension_for_path (const gchar *path) +{ + gint width = 0; + gint height = 0; + + if (path == NULL) + return 0; + + /* Do not infer the thumbnail size from the cache directory name. + * Existing cache files can be smaller than their freedesktop cache class + * allows, for example a real 32x32 PNG inside thumbnails/normal/. + */ + if (gdk_pixbuf_get_file_info (path, &width, &height) == NULL) + return 0; + + return MAX (width, height); +} + + +static gboolean +thumbnail_debug_enabled (void) +{ + static gint enabled = -1; + + if (enabled == -1) + enabled = g_getenv ("GTK_FILE_CHOOSER_THUMBNAIL_DEBUG") != NULL; + + return enabled; +} + +static void +thumbnail_debug_log_file (const gchar *where, + GFile *file, + const gchar *format, + ...) +{ + va_list args; + gchar *message; + gchar *uri; + + if (!thumbnail_debug_enabled ()) + return; + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + uri = file ? g_file_get_uri (file) : NULL; + + g_debug ("GtkFileChooser thumbnail: %s: %s: %s", + where, + uri ? uri : "(no file)", + message); + + g_free (uri); + g_free (message); +} + +static gchar * +thumbnail_cache_lookup (ThumbnailJob *job) +{ + const gchar *cache_size_dir; + gchar *cache_dir; + gchar *hash; + gchar *basename; + gchar *thumbnail_path; + + if (job->uri == NULL) + return NULL; + + cache_size_dir = thumbnail_cache_size_dir_for_dimension (job->icon_size); + if (cache_size_dir == NULL) + return NULL; + + cache_dir = g_build_filename (g_get_user_cache_dir (), + "thumbnails", + cache_size_dir, + NULL); + hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, job->uri, -1); + basename = g_strconcat (hash, ".png", NULL); + thumbnail_path = g_build_filename (cache_dir, basename, NULL); + + g_free (cache_dir); + g_free (hash); + g_free (basename); + + if (g_file_test (thumbnail_path, G_FILE_TEST_IS_REGULAR)) + return thumbnail_path; + + g_free (thumbnail_path); + return NULL; +} + +static gchar * +thumbnail_cache_save_pixbuf (ThumbnailJob *job, + const gchar *source_path) +{ + GStatBuf st; + const gchar *cache_size_dir; + gchar *cache_dir; + gchar *hash; + gchar *basename; + gchar *thumbnail_path; + gchar *tmp_path; + gchar *mtime_str; + gchar *size_str; + gboolean saved; + gint width; + gint height; + gint max_dimension; + + if (job->pixbuf == NULL || job->uri == NULL || source_path == NULL) + return NULL; + + if (g_stat (source_path, &st) != 0) + return NULL; + + width = gdk_pixbuf_get_width (job->pixbuf); + height = gdk_pixbuf_get_height (job->pixbuf); + max_dimension = MAX (width, height); + + cache_size_dir = thumbnail_cache_size_dir_for_dimension (max_dimension); + if (cache_size_dir == NULL) + return NULL; + + cache_dir = g_build_filename (g_get_user_cache_dir (), + "thumbnails", + cache_size_dir, + NULL); + + if (g_mkdir_with_parents (cache_dir, 0700) != 0) + { + g_free (cache_dir); + return NULL; + } + + hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, job->uri, -1); + basename = g_strconcat (hash, ".png", NULL); + thumbnail_path = g_build_filename (cache_dir, basename, NULL); + tmp_path = g_strdup_printf ("%s.%08x.tmp", thumbnail_path, g_random_int ()); + + mtime_str = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) st.st_mtime); + size_str = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) st.st_size); + + saved = gdk_pixbuf_save (job->pixbuf, + tmp_path, + "png", + NULL, + "tEXt::Thumb::URI", job->uri, + "tEXt::Thumb::MTime", mtime_str, + "tEXt::Thumb::Size", size_str, + "tEXt::Software", "gtk2-ng", + NULL); + + if (saved && g_rename (tmp_path, thumbnail_path) == 0) + { + g_free (cache_dir); + g_free (hash); + g_free (basename); + g_free (tmp_path); + g_free (mtime_str); + g_free (size_str); + + return thumbnail_path; + } + + g_remove (tmp_path); + + g_free (cache_dir); + g_free (hash); + g_free (basename); + g_free (thumbnail_path); + g_free (tmp_path); + g_free (mtime_str); + g_free (size_str); + + return NULL; +} + +static gpointer thumbnail_worker_thread (gpointer data); + +static void +thumbnail_job_free (ThumbnailJob *job) +{ + if (!job) + return; + + if (job->pixbuf) + g_object_unref (job->pixbuf); + + if (job->cancellable) + g_object_unref (job->cancellable); + + g_free (job->thumbnail_path); + g_free (job->queried_thumbnail_path); + g_free (job->uri); + + if (job->file) + g_object_unref (job->file); + + if (job->impl) + g_object_unref (job->impl); + + g_free (job); +} + +static gboolean +thumbnail_job_apply_cb (gpointer data) +{ + ThumbnailJob *job = data; + GtkFileChooserDefault *impl = job->impl; + + if (!g_cancellable_is_cancelled (job->cancellable) && + impl->show_thumbnails && + job->generation_serial == impl->thumbnail_generation_serial && + impl->current_model && + GTK_IS_FILE_SYSTEM_MODEL (impl->current_model)) + { + GtkFileSystemModel *model; + GtkTreeIter iter; + + model = GTK_FILE_SYSTEM_MODEL (impl->current_model); + + if (_gtk_file_system_model_get_iter_for_file (model, &iter, job->file)) + { + GFileInfo *info; + + info = g_file_info_dup (_gtk_file_system_model_get_info (model, &iter)); + if (job->pixbuf || job->thumbnail_path) + { + if (job->pixbuf) + { + g_file_info_set_attribute_object (info, + FILECHOOSER_GENERATED_THUMBNAIL, + G_OBJECT (job->pixbuf)); + g_file_info_set_attribute_int32 (info, + FILECHOOSER_GENERATED_THUMBNAIL_SIZE, + job->icon_size); + } + + if (job->thumbnail_path) + { + const gchar *current_thumbnail_path; + gchar *debug_current_thumbnail_path; + gboolean accepted_thumbnail_path; + gint current_dimension; + gint job_dimension; + + current_thumbnail_path = g_file_info_get_attribute_byte_string (info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + debug_current_thumbnail_path = NULL; + if (thumbnail_debug_enabled ()) + debug_current_thumbnail_path = g_strdup (current_thumbnail_path); + + current_dimension = thumbnail_cache_max_dimension_for_path (current_thumbnail_path); + job_dimension = thumbnail_cache_max_dimension_for_path (job->thumbnail_path); + + /* Prefer the smallest thumbnail that is still sufficient for + * the current target size. This allows replacing an existing + * x-large thumbnail with a normal one at 64px icon size, but + * still prevents replacing a sufficient large thumbnail with + * an undersized one at higher zoom levels. + */ + accepted_thumbnail_path = (!current_thumbnail_path || + job_dimension >= current_dimension || + job_dimension >= job->icon_size); + + if (accepted_thumbnail_path) + g_file_info_set_attribute_byte_string (info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH, + job->thumbnail_path); + + if (thumbnail_debug_enabled ()) + { + thumbnail_debug_log_file ("worker-path-decision", + job->file, + "target-size=%d current=%s current-dim=%d job=%s job-dim=%d accepted=%d", + job->icon_size, + debug_current_thumbnail_path ? debug_current_thumbnail_path : "(none)", + current_dimension, + job->thumbnail_path, + job_dimension, + accepted_thumbnail_path); + g_free (debug_current_thumbnail_path); + } + } + } + else if (job->failed) + { + g_file_info_set_attribute_boolean (info, + "filechooser::thumbnail_generation_failed", + TRUE); + } + + thumbnail_debug_log_file ("worker-apply", + job->file, + "target-size=%d pixbuf=%dx%d path=%s failed=%d", + job->icon_size, + job->pixbuf ? gdk_pixbuf_get_width (job->pixbuf) : 0, + job->pixbuf ? gdk_pixbuf_get_height (job->pixbuf) : 0, + job->thumbnail_path ? job->thumbnail_path : "(none)", + job->failed); + + _gtk_file_system_model_clear_cache_for_iter (model, + &iter, + MODEL_COL_LIST_PIXBUF); + _gtk_file_system_model_clear_cache_for_iter (model, + &iter, + MODEL_COL_ICON_PIXBUF); + + _gtk_file_system_model_update_file (model, job->file, info); + g_object_unref (info); + } + } + + impl->thumbnail_worker_running = FALSE; + + if (impl->thumbnail_queue) + thumbnail_queue_schedule (impl); + + thumbnail_job_free (job); + + return FALSE; +} + +static gpointer +thumbnail_worker_thread (gpointer data) +{ + ThumbnailJob *job = data; + gchar *path; + + job->uri = g_file_get_uri (job->file); + job->thumbnail_path = thumbnail_cache_lookup (job); + + if (job->thumbnail_path && !g_cancellable_is_cancelled (job->cancellable)) + job->pixbuf = gdk_pixbuf_new_from_file_at_scale (job->thumbnail_path, + job->icon_size, + job->icon_size, + TRUE, + NULL); + + if (!job->pixbuf && job->thumbnail_path) + { + g_free (job->thumbnail_path); + job->thumbnail_path = NULL; + } + + if (!job->pixbuf && job->queried_thumbnail_path && !g_cancellable_is_cancelled (job->cancellable)) + { + job->pixbuf = gdk_pixbuf_new_from_file_at_scale (job->queried_thumbnail_path, + job->icon_size, + job->icon_size, + TRUE, + NULL); + + if (job->pixbuf) + job->thumbnail_path = g_strdup (job->queried_thumbnail_path); + } + + if (job->pixbuf) + { + gdk_threads_add_idle (thumbnail_job_apply_cb, job); + return NULL; + } + + path = g_file_get_path (job->file); + + if (path && !g_cancellable_is_cancelled (job->cancellable)) + { + /* Thumbnail generation intentionally goes through GdkPixbuf loaders. + * Formats such as WebP, AVIF, HEIF or JPEG XL work when the matching + * system GdkPixbuf loader is installed; gtk2-ng should not duplicate + * image decoders in the file chooser itself. + */ + job->pixbuf = gdk_pixbuf_new_from_file_at_scale (path, + job->icon_size, + job->icon_size, + TRUE, + NULL); + job->failed = job->pixbuf == NULL; + + if (job->pixbuf) + job->thumbnail_path = thumbnail_cache_save_pixbuf (job, path); + } + else + { + job->failed = TRUE; + } + + g_free (path); + + gdk_threads_add_idle (thumbnail_job_apply_cb, job); + + return NULL; +} + + +static gchar * +thumbnail_queue_get_current_thumbnail_path (GtkFileChooserDefault *impl, + GFile *file) +{ + GtkFileSystemModel *model; + GtkTreeIter iter; + GFileInfo *info; + const gchar *thumbnail_path; + + if (!impl->current_model || !GTK_IS_FILE_SYSTEM_MODEL (impl->current_model)) + return NULL; + + model = GTK_FILE_SYSTEM_MODEL (impl->current_model); + + if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) + return NULL; + + info = _gtk_file_system_model_get_info (model, &iter); + if (info == NULL) + return NULL; + + thumbnail_path = g_file_info_get_attribute_byte_string (info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + + return g_strdup (thumbnail_path); +} + +static gboolean +thumbnail_worker_start (GtkFileChooserDefault *impl, + GFile *file) +{ + ThumbnailJob *job; +#if GLIB_CHECK_VERSION (2, 32, 0) + GThread *thread; +#else + GError *error = NULL; +#endif + + if (!impl->thumbnail_cancellable) + return FALSE; + + job = g_new0 (ThumbnailJob, 1); + job->impl = g_object_ref (impl); + job->file = file; /* takes ownership of the queue reference */ + job->cancellable = g_object_ref (impl->thumbnail_cancellable); + job->generation_serial = impl->thumbnail_generation_serial; + job->icon_size = thumbnail_queue_get_cache_size (impl); + job->queried_thumbnail_path = thumbnail_queue_get_current_thumbnail_path (impl, file); + + thumbnail_debug_log_file ("worker-start", + job->file, + "target-size=%d", + job->icon_size); + +#if GLIB_CHECK_VERSION (2, 32, 0) + thread = g_thread_new ("gtk-filechooser-thumbnail", + thumbnail_worker_thread, + job); + g_thread_unref (thread); +#else + if (!g_thread_create (thumbnail_worker_thread, job, FALSE, &error)) + { + if (error) + g_error_free (error); + + thumbnail_job_free (job); + return FALSE; + } +#endif + + return TRUE; +} + +static gboolean +thumbnail_queue_file_still_needed (GtkFileChooserDefault *impl, + GFile *file) +{ + GtkFileSystemModel *model; + GtkTreeIter iter; + GFileInfo *info; + + if (!impl->current_model || !GTK_IS_FILE_SYSTEM_MODEL (impl->current_model)) + return FALSE; + + model = GTK_FILE_SYSTEM_MODEL (impl->current_model); + + if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) + return FALSE; + + info = _gtk_file_system_model_get_info (model, &iter); + + return thumbnail_queue_should_enqueue (impl, file, info); +} + +static gboolean +thumbnail_queue_idle_cb (gpointer data) +{ + GtkFileChooserDefault *impl = data; + GFile *file; + + impl->thumbnail_queue_idle_id = 0; + + if (!impl->show_thumbnails) + { + impl->thumbnail_worker_running = FALSE; + return FALSE; + } + + file = thumbnail_queue_pop_next (impl); + if (file == NULL) + { + impl->thumbnail_worker_running = FALSE; + return FALSE; + } + + if (!thumbnail_queue_file_still_needed (impl, file)) + { + g_object_unref (file); + impl->thumbnail_worker_running = FALSE; + + if (impl->thumbnail_queue) + thumbnail_queue_schedule (impl); + + return FALSE; + } + + if (!thumbnail_worker_start (impl, file)) + { + g_object_unref (file); + impl->thumbnail_worker_running = FALSE; + + if (impl->thumbnail_queue) + thumbnail_queue_schedule (impl); + } + + return FALSE; +} + +static void +thumbnail_queue_schedule (GtkFileChooserDefault *impl) +{ + if (impl->thumbnail_queue_idle_id || impl->thumbnail_worker_running) + return; + + if (!impl->show_thumbnails || impl->thumbnail_queue == NULL) + return; + + impl->thumbnail_worker_running = TRUE; + impl->thumbnail_queue_idle_id = gdk_threads_add_idle (thumbnail_queue_idle_cb, + impl); +} + +static void +thumbnail_queue_promote (GtkFileChooserDefault *impl, + GFile *file) +{ + GSList *prev; + GSList *link; + + prev = NULL; + link = impl->thumbnail_queue; + + while (link) + { + if (g_file_equal (G_FILE (link->data), file)) + break; + + prev = link; + link = link->next; + } + + if (link == NULL || prev == NULL) + return; + + prev->next = link->next; + + if (impl->thumbnail_queue_tail == link) + impl->thumbnail_queue_tail = prev; + + link->next = impl->thumbnail_queue; + impl->thumbnail_queue = link; +} + +static void +thumbnail_queue_enqueue (GtkFileChooserDefault *impl, + GFile *file, + GFileInfo *info, + gboolean high_priority) +{ + GSList *link; + if (!thumbnail_queue_should_enqueue (impl, file, info)) + return; + + thumbnail_queue_ensure (impl); + + if (g_hash_table_lookup (impl->thumbnail_queued_files, file)) + { + if (high_priority) + thumbnail_queue_promote (impl, file); + + return; + } + + g_hash_table_insert (impl->thumbnail_queued_files, + g_object_ref (file), + GUINT_TO_POINTER (1)); + link = g_slist_alloc (); + link->data = g_object_ref (file); + link->next = NULL; + + if (high_priority || impl->thumbnail_queue == NULL) + { + link->next = impl->thumbnail_queue; + impl->thumbnail_queue = link; + + if (impl->thumbnail_queue_tail == NULL) + impl->thumbnail_queue_tail = link; + } + else + { + impl->thumbnail_queue_tail->next = link; + impl->thumbnail_queue_tail = link; + } + + thumbnail_queue_schedule (impl); +} + static void gtk_file_chooser_default_finalize (GObject *object) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object); GSList *l; + if (impl->list_tooltip_path) + { + gtk_tree_path_free (impl->list_tooltip_path); + impl->list_tooltip_path = NULL; + } + + impl->list_tooltip_model = NULL; + + g_free (impl->list_tooltip_text); + impl->list_tooltip_text = NULL; + icon_view_scale_remove_timeout (impl); + if (impl->icon_view_visible_range_idle_id) + { + g_source_remove (impl->icon_view_visible_range_idle_id); + impl->icon_view_visible_range_idle_id = 0; + } + + thumbnail_queue_clear (impl); + thumbnail_standard_icon_cache_clear (impl); + + if (impl->file_clipboard_cut_files) + { + g_hash_table_destroy (impl->file_clipboard_cut_files); + impl->file_clipboard_cut_files = NULL; + } + + if (impl->icon_tooltip_path) + { + gtk_tree_path_free (impl->icon_tooltip_path); + impl->icon_tooltip_path = NULL; + } + + g_free (impl->icon_tooltip_text); + impl->icon_tooltip_text = NULL; + + + if (impl->preview_panel_update_timeout_id) + { + g_source_remove (impl->preview_panel_update_timeout_id); + impl->preview_panel_update_timeout_id = 0; + } + + if (impl->preview_panel_resize_timeout_id) + { + g_source_remove (impl->preview_panel_resize_timeout_id); + impl->preview_panel_resize_timeout_id = 0; + } + unset_file_system_backend (impl); if (impl->shortcuts_pane_filter_model) @@ -917,11 +1908,23 @@ gtk_file_chooser_default_finalize (GObject *object) search_clear_model (impl, FALSE); recent_clear_model (impl, FALSE); - /* stopping the load above should have cleared this */ - g_assert (impl->load_timeout_id == 0); - g_free (impl->preview_display_name); + if (impl->rename_idle) + { + g_source_destroy (impl->rename_idle); + impl->rename_idle = NULL; + } + + if (impl->rename_file) + { + g_object_unref (impl->rename_file); + impl->rename_file = NULL; + } + + g_free (impl->rename_new_text); + g_free (impl->rename_old_text); + g_free (impl->edited_new_text); G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->finalize (object); @@ -1042,6 +2045,17 @@ error_creating_folder_dialog (GtkFileChooserDefault *impl, file, error); } +/* Shows an error dialog about not being able to rename a file */ +static void +error_renaming_file_dialog (GtkFileChooserDefault *impl, + GFile *file, + GError *error) +{ + error_dialog (impl, + _("The file could not be renamed"), + file, error); +} + /* Shows an error about not being able to create a folder because a file with * the same name is already there. */ @@ -1094,6 +2108,80 @@ error_building_filename_dialog (GtkFileChooserDefault *impl, NULL, error); } +static gchar * +paste_conflict_rename_dialog (GtkFileChooserDefault *impl, + const gchar *basename) +{ + GtkWindow *parent; + GtkWidget *dialog; + GtkWidget *content_area; + GtkWidget *box; + GtkWidget *label; + GtkWidget *entry; + gchar *result = NULL; + + parent = get_toplevel (GTK_WIDGET (impl)); + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_CANCEL, + _("A file named \"%s\" already exists."), + basename ? basename : _("file")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", + _("Choose a new name for the pasted file.")); + + gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Rename"), GTK_RESPONSE_ACCEPT); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (content_area), box, FALSE, FALSE, 0); + gtk_widget_show (box); + + label = gtk_label_new (_("New name:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry), basename ? basename : ""); + gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); + gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0); + gtk_widget_show (entry); + + if (parent && gtk_window_has_group (parent)) + gtk_window_group_add_window (gtk_window_get_group (parent), + GTK_WINDOW (dialog)); + + gtk_widget_grab_focus (entry); + + while (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) + { + const gchar *name; + + name = gtk_entry_get_text (GTK_ENTRY (entry)); + + if (name && *name && + strchr (name, G_DIR_SEPARATOR) == NULL && + strchr (name, '/') == NULL) + { + result = g_strdup (name); + break; + } + + error_message (impl, + _("Invalid file name"), + _("The name must not be empty and must not contain a slash.")); + } + + gtk_widget_destroy (dialog); + + return result; +} + /* Shows an error dialog when we cannot switch to a folder */ static void error_changing_folder_dialog (GtkFileChooserDefault *impl, @@ -1147,24 +2235,33 @@ update_preview_widget_visibility (GtkFileChooserDefault *impl) if (impl->use_preview_label) { if (!impl->preview_label) - { - impl->preview_label = gtk_label_new (impl->preview_display_name); - gtk_box_pack_start (GTK_BOX (impl->preview_box), impl->preview_label, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (impl->preview_box), impl->preview_label, 0); - gtk_label_set_ellipsize (GTK_LABEL (impl->preview_label), PANGO_ELLIPSIZE_MIDDLE); - gtk_widget_show (impl->preview_label); - } + { + impl->preview_label = gtk_label_new (impl->preview_display_name); + gtk_box_pack_start (GTK_BOX (impl->preview_box), impl->preview_label, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (impl->preview_box), impl->preview_label, 0); + gtk_label_set_ellipsize (GTK_LABEL (impl->preview_label), PANGO_ELLIPSIZE_MIDDLE); + gtk_widget_show (impl->preview_label); + } } else { if (impl->preview_label) - { - gtk_widget_destroy (impl->preview_label); - impl->preview_label = NULL; - } + { + gtk_widget_destroy (impl->preview_label); + impl->preview_label = NULL; + } } - if (impl->preview_widget_active && impl->preview_widget) + if (impl->preview_panel) + { + if (impl->preview_panel_active) + gtk_widget_show (impl->preview_panel); + else + gtk_widget_hide (impl->preview_panel); + } + + if ((impl->preview_widget_active && impl->preview_widget) || + (impl->preview_panel_active && impl->preview_panel)) gtk_widget_show (impl->preview_box); else gtk_widget_hide (impl->preview_box); @@ -2369,7 +3466,7 @@ new_folder_button_clicked (GtkButton *button, TRUE); gtk_tree_path_free (path); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { add_idle_to_edit_icon_view (impl, path); } @@ -2390,6 +3487,129 @@ add_idle_while_impl_is_alive (GtkFileChooserDefault *impl, GCallback callback) return source; } +static GtkCellRenderer * +file_list_get_name_renderer (GtkFileChooserDefault *impl) +{ + if (impl->view_mode == VIEW_MODE_LIST) + { + GList *renderers; + GList *l; + GtkCellRenderer *renderer = NULL; + + if (impl->list_name_column == NULL) + return NULL; + + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (impl->list_name_column)); + + for (l = renderers; l; l = l->next) + { + if (GTK_IS_CELL_RENDERER_TEXT (l->data)) + { + renderer = GTK_CELL_RENDERER (l->data); + break; + } + } + + g_list_free (renderers); + return renderer; + } + + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) + return impl->list_name_renderer; + + return NULL; +} + +static void +rename_clear_state (GtkFileChooserDefault *impl) +{ + GtkCellRenderer *renderer; + + if (impl->rename_idle) + { + g_source_destroy (impl->rename_idle); + impl->rename_idle = NULL; + } + + if (impl->rename_file) + { + g_object_unref (impl->rename_file); + impl->rename_file = NULL; + } + + g_free (impl->rename_new_text); + impl->rename_new_text = NULL; + + g_free (impl->rename_old_text); + impl->rename_old_text = NULL; + + renderer = file_list_get_name_renderer (impl); + if (renderer) + g_object_set (renderer, "editable", FALSE, NULL); +} + +static gboolean +rename_idle_cb (GtkFileChooserDefault *impl) +{ + GFile *file; + gchar *new_text; + gchar *old_text; + GtkCellRenderer *renderer; + + GDK_THREADS_ENTER (); + + g_source_destroy (impl->rename_idle); + impl->rename_idle = NULL; + + file = impl->rename_file; + impl->rename_file = NULL; + + new_text = impl->rename_new_text; + impl->rename_new_text = NULL; + + old_text = impl->rename_old_text; + impl->rename_old_text = NULL; + + renderer = file_list_get_name_renderer (impl); + if (renderer) + g_object_set (renderer, "editable", FALSE, NULL); + + if (file && new_text && *new_text && + g_strcmp0 (new_text, old_text) != 0) + { + GError *error = NULL; + GFile *renamed; + + renamed = g_file_set_display_name (file, new_text, NULL, &error); + + if (renamed) + g_object_unref (renamed); + else + error_renaming_file_dialog (impl, file, error); + } + + if (file) + g_object_unref (file); + + g_free (new_text); + g_free (old_text); + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +queue_rename_idle (GtkFileChooserDefault *impl, + const gchar *new_text) +{ + if (!impl->rename_idle) + impl->rename_idle = add_idle_while_impl_is_alive (impl, G_CALLBACK (rename_idle_cb)); + + g_free (impl->rename_new_text); + impl->rename_new_text = g_strdup (new_text); +} + /* Idle handler for creating a new folder after editing its name cell, or for * canceling the editing. */ @@ -2457,14 +3677,18 @@ queue_edited_idle (GtkFileChooserDefault *impl, /* Callback used from the text cell renderer when the new folder is named */ static void renderer_edited_cb (GtkCellRendererText *cell_renderer_text, - const gchar *path, - const gchar *new_text, - GtkFileChooserDefault *impl) + const gchar *path, + const gchar *new_text, + GtkFileChooserDefault *impl) { /* work around bug #154921 */ g_object_set (cell_renderer_text, - "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); - queue_edited_idle (impl, new_text); + "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + + if (impl->rename_file) + queue_rename_idle (impl, new_text); + else + queue_edited_idle (impl, new_text); } /* Callback used from the icon view text renderer to center editable text */ @@ -2483,12 +3707,16 @@ renderer_editing_started_cb (GtkCellRendererText *cell_renderer_text, */ static void renderer_editing_canceled_cb (GtkCellRendererText *cell_renderer_text, - GtkFileChooserDefault *impl) + GtkFileChooserDefault *impl) { /* work around bug #154921 */ g_object_set (cell_renderer_text, - "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); - queue_edited_idle (impl, NULL); + "mode", GTK_CELL_RENDERER_MODE_INERT, NULL); + + if (impl->rename_file) + rename_clear_state (impl); + else + queue_edited_idle (impl, NULL); } /* Creates the widgets for the filter combo box */ @@ -2958,6 +4186,8 @@ static void shortcuts_check_popup_sensitivity (GtkFileChooserDefault *impl) { GtkTreeIter iter; + ShortcutType shortcut_type = SHORTCUT_TYPE_SEPARATOR; + gpointer col_data = NULL; gboolean removable = FALSE; if (impl->browse_shortcuts_popup_menu == NULL) @@ -2965,11 +4195,27 @@ shortcuts_check_popup_sensitivity (GtkFileChooserDefault *impl) if (shortcuts_get_selected (impl, &iter)) gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, + SHORTCUTS_COL_DATA, &col_data, + SHORTCUTS_COL_TYPE, &shortcut_type, SHORTCUTS_COL_REMOVABLE, &removable, -1); gtk_widget_set_sensitive (impl->browse_shortcuts_popup_menu_remove_item, removable); gtk_widget_set_sensitive (impl->browse_shortcuts_popup_menu_rename_item, removable); + if (impl->browse_shortcuts_popup_menu_open_containing_folder_item) + { + gboolean show_item; + + show_item = shortcut_type == SHORTCUT_TYPE_FILE && col_data != NULL; + + if (show_item) + gtk_widget_show (impl->browse_shortcuts_popup_menu_open_containing_folder_item); + else + gtk_widget_hide (impl->browse_shortcuts_popup_menu_open_containing_folder_item); + + gtk_widget_set_sensitive (impl->browse_shortcuts_popup_menu_open_containing_folder_item, + show_item); + } } /* GtkWidget::drag-begin handler for the shortcuts list. */ @@ -3471,6 +4717,7 @@ shortcuts_popup_menu_detach_cb (GtkWidget *attach_widget, impl->browse_shortcuts_popup_menu = NULL; impl->browse_shortcuts_popup_menu_remove_item = NULL; impl->browse_shortcuts_popup_menu_rename_item = NULL; + impl->browse_shortcuts_popup_menu_open_containing_folder_item = NULL; } static void @@ -3512,6 +4759,10 @@ rename_shortcut_cb (GtkMenuItem *item, } /* Constructs the popup menu for the file list if needed */ +static void +open_shortcut_containing_folder_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl); + static void shortcuts_build_popup_menu (GtkFileChooserDefault *impl) { @@ -3541,6 +4792,15 @@ shortcuts_build_popup_menu (GtkFileChooserDefault *impl) gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), item); + item = gtk_image_menu_item_new_with_mnemonic (_("Open _Containing Folder")); + impl->browse_shortcuts_popup_menu_open_containing_folder_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (open_shortcut_containing_folder_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), item); + item = gtk_separator_menu_item_new (); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_shortcuts_popup_menu), item); @@ -3876,6 +5136,34 @@ browse_files_key_press_event_cb (GtkWidget *widget, impl = (GtkFileChooserDefault *) data; + if ((event->state & gtk_accelerator_get_default_mod_mask ()) == GDK_CONTROL_MASK) + { + switch (event->keyval) + { + case GDK_KEY_c: + copy_files_cb (NULL, impl); + return TRUE; + + case GDK_KEY_x: + cut_files_cb (NULL, impl); + return TRUE; + + case GDK_KEY_v: + paste_files_cb (NULL, impl); + return TRUE; + + default: + break; + } + } + + if (event->keyval == GDK_KEY_F2 && + (event->state & gtk_accelerator_get_default_mod_mask ()) == 0) + { + rename_selected_file (impl); + return TRUE; + } + if ((event->keyval == GDK_KEY_slash || event->keyval == GDK_KEY_KP_Divide #ifdef G_OS_UNIX @@ -3937,7 +5225,119 @@ popup_menu_detach_cb (GtkWidget *attach_widget, impl->browse_files_popup_menu = NULL; impl->browse_files_popup_menu_add_shortcut_item = NULL; + impl->browse_files_popup_menu_copy_item = NULL; + impl->browse_files_popup_menu_cut_item = NULL; + impl->browse_files_popup_menu_paste_item = NULL; + impl->browse_files_popup_menu_copy_location_item = NULL; + impl->browse_files_popup_menu_open_containing_folder_item = NULL; + impl->browse_files_popup_menu_rename_item = NULL; + impl->browse_files_popup_menu_trash_item = NULL; impl->browse_files_popup_menu_hidden_files_item = NULL; + impl->browse_files_popup_menu_preview_panel_item = NULL; +} + +struct rename_selected_file_closure +{ + GFile *file; + GtkTreePath *path; + gchar *old_text; + guint count; +}; + +static void +rename_selected_file_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct rename_selected_file_closure *closure = data; + + closure->count++; + + if (closure->count != 1) + return; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &closure->file, + MODEL_COL_NAME, &closure->old_text, + -1); + closure->path = gtk_tree_path_copy (path); +} + +static void +rename_selected_file (GtkFileChooserDefault *impl) +{ + struct rename_selected_file_closure closure; + GtkCellRenderer *renderer; + + if (impl->operation_mode != OPERATION_MODE_BROWSE) + return; + + if (current_selection_count_selected_rows (impl) != 1) + return; + + closure.file = NULL; + closure.path = NULL; + closure.old_text = NULL; + closure.count = 0; + + current_selection_selected_foreach (impl, + rename_selected_file_foreach_cb, + &closure); + + if (closure.count != 1 || closure.file == NULL || closure.path == NULL) + { + if (closure.file) + g_object_unref (closure.file); + if (closure.path) + gtk_tree_path_free (closure.path); + g_free (closure.old_text); + return; + } + + renderer = file_list_get_name_renderer (impl); + if (renderer == NULL) + { + g_object_unref (closure.file); + gtk_tree_path_free (closure.path); + g_free (closure.old_text); + return; + } + + rename_clear_state (impl); + impl->rename_file = closure.file; + impl->rename_old_text = closure.old_text; + + g_object_set (renderer, "editable", TRUE, NULL); + + if (impl->view_mode == VIEW_MODE_LIST) + { + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->browse_files_tree_view), + closure.path, impl->list_name_column, + FALSE, 0.0, 0.0); + gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (impl->browse_files_tree_view), + closure.path, + impl->list_name_column, + renderer, + TRUE); + gtk_tree_path_free (closure.path); + } + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) + { + add_idle_to_edit_icon_view (impl, closure.path); + } + else + { + gtk_tree_path_free (closure.path); + g_assert_not_reached (); + } +} + +static void +rename_file_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + rename_selected_file (impl); } /* Callback used when the "Add to Bookmarks" menu item is activated */ @@ -3948,6 +5348,1617 @@ add_to_shortcuts_cb (GtkMenuItem *item, bookmarks_add_selected_folder (impl); } +enum { + FILE_CLIPBOARD_TARGET_GNOME_COPIED_FILES, + FILE_CLIPBOARD_TARGET_URI_LIST, + FILE_CLIPBOARD_TARGET_TEXT_PLAIN, + FILE_CLIPBOARD_TARGET_KDE_CUT_SELECTION, + FILE_CLIPBOARD_TARGET_TDE_CUT_SELECTION, + FILE_CLIPBOARD_TARGET_EMPTY +}; + +struct file_clipboard_data +{ + gchar *gnome_copied_files; + gchar *uri_list; + gchar *plain_text; + gchar *cut_selection; + GtkFileChooserDefault *impl; +}; + +struct file_clipboard_closure +{ + GString *gnome_copied_files; + GString *uri_list; + GString *plain_text; + GHashTable *cut_files; + gboolean first; +}; + +static const GtkTargetEntry file_clipboard_targets[] = { + { "x-special/gnome-copied-files", 0, FILE_CLIPBOARD_TARGET_GNOME_COPIED_FILES }, + { "text/uri-list", 0, FILE_CLIPBOARD_TARGET_URI_LIST }, + { "text/plain", 0, FILE_CLIPBOARD_TARGET_TEXT_PLAIN }, + { "text/plain;charset=utf-8", 0, FILE_CLIPBOARD_TARGET_TEXT_PLAIN }, + { "text/plain;charset=UTF-8", 0, FILE_CLIPBOARD_TARGET_TEXT_PLAIN }, + { "UTF8_STRING", 0, FILE_CLIPBOARD_TARGET_TEXT_PLAIN }, + { "text/x-moz-url", 0, FILE_CLIPBOARD_TARGET_URI_LIST }, + { "application/x-kde4-urilist", 0, FILE_CLIPBOARD_TARGET_URI_LIST }, + { "application/x-tde-urilist", 0, FILE_CLIPBOARD_TARGET_URI_LIST }, + { "application/x-kde-cutselection", 0, FILE_CLIPBOARD_TARGET_KDE_CUT_SELECTION }, + { "application/x-tde-cutselection", 0, FILE_CLIPBOARD_TARGET_TDE_CUT_SELECTION }, + { "application/x-tdeio-metadata", 0, FILE_CLIPBOARD_TARGET_EMPTY } +}; + +static void +file_clipboard_queue_draw_current_view (GtkFileChooserDefault *impl) +{ + if (impl->browse_files_current_view) + gtk_widget_queue_draw (impl->browse_files_current_view); +} + +static void +file_clipboard_cut_files_clear (GtkFileChooserDefault *impl) +{ + if (!impl->file_clipboard_cut_files) + return; + + g_hash_table_destroy (impl->file_clipboard_cut_files); + impl->file_clipboard_cut_files = NULL; + + file_clipboard_queue_draw_current_view (impl); +} + +static gboolean +file_clipboard_file_is_cut (GtkFileChooserDefault *impl, + GFile *file) +{ + return (impl->file_clipboard_cut_files && + file && + g_hash_table_lookup (impl->file_clipboard_cut_files, file) != NULL); +} + +static void +file_clipboard_icon_cell_data_func (GtkCellLayout *cell_layout, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GtkFileChooserDefault *impl = data; + GFile *file = NULL; + gboolean is_sensitive = TRUE; + gboolean is_hidden = FALSE; + gboolean is_cut; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + MODEL_COL_IS_SENSITIVE, &is_sensitive, + MODEL_COL_IS_HIDDEN, &is_hidden, + -1); + + is_cut = file_clipboard_file_is_cut (impl, file); + + /* + * Match common file-manager feedback for cut and hidden files: dim only + * the icon. The row remains selectable and activatable; text, size and + * mtime stay normally readable. + */ + g_object_set (renderer, + "sensitive", is_sensitive && !is_cut && !is_hidden, + NULL); + + if (file) + g_object_unref (file); +} + +static void +file_clipboard_data_free (struct file_clipboard_data *data) +{ + if (!data) + return; + + if (data->impl) + g_object_remove_weak_pointer (G_OBJECT (data->impl), + (gpointer *) &data->impl); + + g_free (data->gnome_copied_files); + g_free (data->uri_list); + g_free (data->plain_text); + g_free (data->cut_selection); + g_free (data); +} + +static void +file_clipboard_get_cb (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer user_data) +{ + struct file_clipboard_data *data = user_data; + const gchar *text; + + switch (info) + { + case FILE_CLIPBOARD_TARGET_GNOME_COPIED_FILES: + text = data->gnome_copied_files; + break; + + case FILE_CLIPBOARD_TARGET_TEXT_PLAIN: + text = data->plain_text; + break; + + case FILE_CLIPBOARD_TARGET_KDE_CUT_SELECTION: + case FILE_CLIPBOARD_TARGET_TDE_CUT_SELECTION: + text = data->cut_selection; + break; + + case FILE_CLIPBOARD_TARGET_EMPTY: + text = ""; + break; + + case FILE_CLIPBOARD_TARGET_URI_LIST: + default: + text = data->uri_list; + break; + } + + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), + 8, + (const guchar *) text, + strlen (text) + (info == FILE_CLIPBOARD_TARGET_URI_LIST ? 1 : 0)); +} + +static void +file_clipboard_clear_cb (GtkClipboard *clipboard, + gpointer user_data) +{ + struct file_clipboard_data *data = user_data; + + if (data->impl) + file_clipboard_cut_files_clear (data->impl); + + file_clipboard_data_free (data); +} + +static void +file_clipboard_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct file_clipboard_closure *closure = data; + GFile *file = NULL; + gchar *uri; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + if (file == NULL) + return; + + uri = g_file_get_uri (file); + + if (uri) + { + g_string_append (closure->gnome_copied_files, uri); + g_string_append_c (closure->gnome_copied_files, '\n'); + + g_string_append (closure->uri_list, uri); + g_string_append (closure->uri_list, "\r\n"); + + if (!closure->first) + g_string_append_c (closure->plain_text, '\n'); + + g_string_append (closure->plain_text, uri); + closure->first = FALSE; + + if (closure->cut_files) + g_hash_table_insert (closure->cut_files, + g_object_ref (file), + GUINT_TO_POINTER (1)); + } + + g_free (uri); + g_object_unref (file); +} + +static void +copy_or_cut_files_to_clipboard (GtkFileChooserDefault *impl, + gboolean cut) +{ + struct file_clipboard_closure closure; + struct file_clipboard_data *data; + GtkClipboard *clipboard; + + closure.gnome_copied_files = g_string_new (cut ? "cut\n" : "copy\n"); + closure.uri_list = g_string_new (NULL); + closure.plain_text = g_string_new (NULL); + closure.cut_files = cut ? g_hash_table_new_full ((GHashFunc) g_file_hash, + (GEqualFunc) g_file_equal, + g_object_unref, + NULL) : NULL; + closure.first = TRUE; + + current_selection_selected_foreach (impl, + file_clipboard_foreach_cb, + &closure); + + if (closure.uri_list->len == 0) + { + g_string_free (closure.gnome_copied_files, TRUE); + g_string_free (closure.uri_list, TRUE); + g_string_free (closure.plain_text, TRUE); + if (closure.cut_files) + g_hash_table_destroy (closure.cut_files); + return; + } + + if (closure.gnome_copied_files->len > 0 && + closure.gnome_copied_files->str[closure.gnome_copied_files->len - 1] == '\n') + g_string_truncate (closure.gnome_copied_files, + closure.gnome_copied_files->len - 1); + + data = g_new0 (struct file_clipboard_data, 1); + data->gnome_copied_files = g_string_free (closure.gnome_copied_files, FALSE); + data->uri_list = g_string_free (closure.uri_list, FALSE); + data->plain_text = g_string_free (closure.plain_text, FALSE); + data->cut_selection = g_strdup (cut ? "1" : "0"); + data->impl = impl; + g_object_add_weak_pointer (G_OBJECT (impl), + (gpointer *) &data->impl); + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (impl->browse_files_current_view), + GDK_SELECTION_CLIPBOARD); + + if (gtk_clipboard_set_with_data (clipboard, + file_clipboard_targets, + G_N_ELEMENTS (file_clipboard_targets), + file_clipboard_get_cb, + file_clipboard_clear_cb, + data)) + { + file_clipboard_cut_files_clear (impl); + + if (cut) + { + impl->file_clipboard_cut_files = closure.cut_files; + closure.cut_files = NULL; + } + + file_clipboard_queue_draw_current_view (impl); + + gtk_clipboard_set_can_store (clipboard, + file_clipboard_targets, + G_N_ELEMENTS (file_clipboard_targets)); + } + else + { + file_clipboard_data_free (data); + } + + if (closure.cut_files) + g_hash_table_destroy (closure.cut_files); +} + +static void +copy_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + copy_or_cut_files_to_clipboard (impl, FALSE); +} + +static void +cut_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + copy_or_cut_files_to_clipboard (impl, TRUE); +} + +static gboolean +file_clipboard_has_file_targets (GtkFileChooserDefault *impl) +{ + GtkClipboard *clipboard; + + if (!impl->browse_files_current_view) + return FALSE; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (impl->browse_files_current_view), + GDK_SELECTION_CLIPBOARD); + + return gtk_clipboard_wait_is_target_available (clipboard, + gdk_atom_intern_static_string ("x-special/gnome-copied-files")) || + gtk_clipboard_wait_is_target_available (clipboard, + gdk_atom_intern_static_string ("text/uri-list")); +} + +struct file_clipboard_paste_data +{ + GSList *files; + gboolean cut; +}; + +static void +file_clipboard_file_list_free (GSList *files) +{ + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); +} + +static GSList * +file_clipboard_file_list_copy (GSList *files) +{ + GSList *copy = NULL; + GSList *l; + + for (l = files; l; l = l->next) + copy = g_slist_prepend (copy, g_object_ref (l->data)); + + return g_slist_reverse (copy); +} + +static void +file_clipboard_paste_data_free (struct file_clipboard_paste_data *data) +{ + if (!data) + return; + + file_clipboard_file_list_free (data->files); + g_free (data); +} + +static gboolean +file_clipboard_paste_data_add_uris (struct file_clipboard_paste_data *data, + const gchar *uri_text) +{ + gchar **uris; + gint i; + + if (uri_text == NULL || *uri_text == '\0') + return FALSE; + + uris = g_uri_list_extract_uris (uri_text); + + for (i = 0; uris && uris[i]; i++) + { + GFile *file; + + file = g_file_new_for_uri (uris[i]); + data->files = g_slist_prepend (data->files, file); + } + + g_strfreev (uris); + + if (data->files) + { + data->files = g_slist_reverse (data->files); + return TRUE; + } + + return FALSE; +} + +static struct file_clipboard_paste_data * +file_clipboard_paste_data_from_uris (gchar **uris) +{ + struct file_clipboard_paste_data *data; + gint i; + + if (!uris || !uris[0]) + return NULL; + + data = g_new0 (struct file_clipboard_paste_data, 1); + data->cut = FALSE; + + for (i = 0; uris[i]; i++) + { + GFile *file; + + file = g_file_new_for_uri (uris[i]); + data->files = g_slist_prepend (data->files, file); + } + + if (!data->files) + { + file_clipboard_paste_data_free (data); + return NULL; + } + + data->files = g_slist_reverse (data->files); + + return data; +} + +static struct file_clipboard_paste_data * +file_clipboard_paste_data_from_selection (GtkSelectionData *selection_data, + gboolean has_action) +{ + struct file_clipboard_paste_data *data; + const guchar *selection; + gint length; + gchar *text; + gchar *uris; + + if (selection_data == NULL) + return NULL; + + length = gtk_selection_data_get_length (selection_data); + if (length <= 0) + return NULL; + + selection = gtk_selection_data_get_data (selection_data); + if (selection == NULL) + return NULL; + + text = g_strndup ((const gchar *) selection, length); + data = g_new0 (struct file_clipboard_paste_data, 1); + + if (has_action) + { + gchar *newline; + + newline = strchr (text, '\n'); + if (newline == NULL) + { + g_free (text); + file_clipboard_paste_data_free (data); + return NULL; + } + + *newline = '\0'; + data->cut = strcmp (text, "cut") == 0; + uris = newline + 1; + } + else + { + data->cut = FALSE; + uris = text; + } + + if (!file_clipboard_paste_data_add_uris (data, uris)) + { + g_free (text); + file_clipboard_paste_data_free (data); + return NULL; + } + + g_free (text); + return data; +} + +static gboolean +file_clipboard_cut_selection_is_set (GtkClipboard *clipboard, + const gchar *target_name) +{ + GtkSelectionData *selection_data; + const guchar *selection; + gboolean cut = FALSE; + + selection_data = + gtk_clipboard_wait_for_contents (clipboard, + gdk_atom_intern_static_string (target_name)); + + if (selection_data) + { + selection = gtk_selection_data_get_data (selection_data); + + if (gtk_selection_data_get_length (selection_data) > 0 && + selection && + selection[0] == '1') + cut = TRUE; + + gtk_selection_data_free (selection_data); + } + + return cut; +} + +static struct file_clipboard_paste_data * +file_clipboard_read_paste_data (GtkFileChooserDefault *impl) +{ + GtkClipboard *clipboard; + GtkSelectionData *selection_data; + struct file_clipboard_paste_data *data; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (impl->browse_files_current_view), + GDK_SELECTION_CLIPBOARD); + + selection_data = + gtk_clipboard_wait_for_contents (clipboard, + gdk_atom_intern_static_string ("x-special/gnome-copied-files")); + + data = file_clipboard_paste_data_from_selection (selection_data, TRUE); + + if (selection_data) + gtk_selection_data_free (selection_data); + + if (data) + return data; + + selection_data = + gtk_clipboard_wait_for_contents (clipboard, + gdk_atom_intern_static_string ("text/uri-list")); + + data = file_clipboard_paste_data_from_selection (selection_data, FALSE); + + if (selection_data) + gtk_selection_data_free (selection_data); + + if (data && + (file_clipboard_cut_selection_is_set (clipboard, "application/x-kde-cutselection") || + file_clipboard_cut_selection_is_set (clipboard, "application/x-tde-cutselection"))) + data->cut = TRUE; + + return data; +} + +struct file_paste_operation +{ + GtkFileChooserDefault *impl; + GFile *target_folder; + GSList *files; + gboolean cut; + gboolean made_changes; + gchar *first_destination_name; + GFile *conflict_source; + GSList *conflict_remaining_files; + gchar *conflict_basename; + gchar *error_message; + gchar *error_detail; +}; + +static void +file_paste_operation_free (struct file_paste_operation *op) +{ + if (!op) + return; + + file_clipboard_file_list_free (op->files); + file_clipboard_file_list_free (op->conflict_remaining_files); + + if (op->conflict_source) + g_object_unref (op->conflict_source); + + if (op->target_folder) + g_object_unref (op->target_folder); + + if (op->impl) + g_object_unref (op->impl); + + g_free (op->first_destination_name); + g_free (op->conflict_basename); + g_free (op->error_message); + g_free (op->error_detail); + g_free (op); +} + +static gpointer file_paste_operation_thread (gpointer data); + +static gboolean +file_paste_operation_start_thread (struct file_paste_operation *op) +{ +#if GLIB_CHECK_VERSION (2, 32, 0) + GThread *thread; +#else + GError *error = NULL; +#endif + +#if GLIB_CHECK_VERSION (2, 32, 0) + thread = g_thread_new ("gtk-filechooser-paste", + file_paste_operation_thread, + op); + g_thread_unref (thread); + return TRUE; +#else + if (g_thread_create (file_paste_operation_thread, op, FALSE, &error)) + return TRUE; + + error_message (op->impl, + _("Could not paste files"), + error ? error->message : _("Unknown error")); + + if (error) + g_error_free (error); + + return FALSE; +#endif +} + +static gboolean +file_paste_operation_finish_cb (gpointer data) +{ + struct file_paste_operation *op = data; + + if (op->conflict_source) + { + gchar *new_name; + + new_name = paste_conflict_rename_dialog (op->impl, op->conflict_basename); + + if (new_name) + { + struct file_paste_operation *new_op; + + new_op = g_new0 (struct file_paste_operation, 1); + new_op->impl = g_object_ref (op->impl); + new_op->target_folder = g_object_ref (op->target_folder); + new_op->cut = op->cut; + new_op->made_changes = op->made_changes; + new_op->first_destination_name = new_name; + new_op->files = g_slist_prepend (file_clipboard_file_list_copy (op->conflict_remaining_files), + g_object_ref (op->conflict_source)); + + if (!file_paste_operation_start_thread (new_op)) + file_paste_operation_free (new_op); + + file_paste_operation_free (op); + return FALSE; + } + } + else if (op->error_message) + error_message (op->impl, op->error_message, op->error_detail); + + if (op->made_changes && + op->impl->current_folder && + g_file_equal (op->impl->current_folder, op->target_folder)) + change_folder_and_display_error (op->impl, op->target_folder, FALSE); + + file_paste_operation_free (op); + + return FALSE; +} + +static gpointer +file_paste_operation_thread (gpointer data) +{ + struct file_paste_operation *op = data; + GSList *l; + gboolean first = TRUE; + + for (l = op->files; l; l = l->next) + { + GFile *source = l->data; + GFile *destination; + GError *error = NULL; + gchar *basename; + const gchar *destination_name; + gboolean success; + + basename = g_file_get_basename (source); + if (basename == NULL) + continue; + + destination_name = (first && op->first_destination_name) ? op->first_destination_name : basename; + destination = g_file_get_child (op->target_folder, destination_name); + + if (op->cut) + success = g_file_move (source, + destination, + G_FILE_COPY_NONE, + NULL, + NULL, + NULL, + &error); + else + success = g_file_copy (source, + destination, + G_FILE_COPY_NONE, + NULL, + NULL, + NULL, + &error); + + g_object_unref (destination); + + if (!success) + { + if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + op->conflict_source = g_object_ref (source); + op->conflict_remaining_files = file_clipboard_file_list_copy (l->next); + op->conflict_basename = g_strdup (destination_name); + g_error_free (error); + g_free (basename); + break; + } + + op->error_message = + g_strdup_printf (op->cut ? _("Could not move %s") : _("Could not copy %s"), + basename); + op->error_detail = g_strdup (error ? error->message : _("Unknown error")); + + if (error) + g_error_free (error); + + g_free (basename); + break; + } + + op->made_changes = TRUE; + + g_free (basename); + first = FALSE; + } + + gdk_threads_add_idle (file_paste_operation_finish_cb, op); + + return NULL; +} + + +static void +file_paste_operation_start (GtkFileChooserDefault *impl, + struct file_clipboard_paste_data *paste_data, + GFile *target_folder) +{ + struct file_paste_operation *op; + + if (target_folder == NULL || paste_data == NULL || paste_data->files == NULL) + return; + + op = g_new0 (struct file_paste_operation, 1); + op->impl = g_object_ref (impl); + op->target_folder = g_object_ref (target_folder); + op->files = paste_data->files; + op->cut = paste_data->cut; + + paste_data->files = NULL; + + if (!file_paste_operation_start_thread (op)) + file_paste_operation_free (op); +} + + +static void +paste_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + struct file_clipboard_paste_data *data; + + data = file_clipboard_read_paste_data (impl); + if (data == NULL) + return; + + file_paste_operation_start (impl, data, impl->current_folder); + file_clipboard_paste_data_free (data); +} + +struct file_trash_operation +{ + GtkFileChooserDefault *impl; + GFile *target_folder; + GSList *files; + gchar *error_message; + gchar *error_detail; +}; + +static void +file_trash_operation_free (struct file_trash_operation *op) +{ + if (!op) + return; + + file_clipboard_file_list_free (op->files); + + if (op->target_folder) + g_object_unref (op->target_folder); + + if (op->impl) + g_object_unref (op->impl); + + g_free (op->error_message); + g_free (op->error_detail); + g_free (op); +} + +static gboolean +file_trash_operation_finish_cb (gpointer data) +{ + struct file_trash_operation *op = data; + + if (op->error_message) + error_message (op->impl, op->error_message, op->error_detail); + + if (op->impl->current_folder && + op->target_folder && + g_file_equal (op->impl->current_folder, op->target_folder)) + change_folder_and_display_error (op->impl, op->target_folder, FALSE); + + file_trash_operation_free (op); + + return FALSE; +} + +static gpointer +file_trash_operation_thread (gpointer data) +{ + struct file_trash_operation *op = data; + GSList *l; + + for (l = op->files; l; l = l->next) + { + GFile *file = l->data; + GError *error = NULL; + gchar *basename; + gboolean success; + + basename = g_file_get_basename (file); + success = g_file_trash (file, NULL, &error); + + if (!success) + { + op->error_message = + g_strdup_printf (_("Could not move %s to the trash"), + basename ? basename : _("file")); + op->error_detail = g_strdup (error ? error->message : _("Unknown error")); + + if (error) + g_error_free (error); + + g_free (basename); + break; + } + + g_free (basename); + } + + gdk_threads_add_idle (file_trash_operation_finish_cb, op); + + return NULL; +} + +static void +trash_selected_file_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct file_trash_operation *op = data; + GFile *file = NULL; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + if (file) + op->files = g_slist_prepend (op->files, file); +} + +static void +trash_selected_files (GtkFileChooserDefault *impl) +{ + struct file_trash_operation *op; +#if GLIB_CHECK_VERSION (2, 32, 0) + GThread *thread; +#else + GError *error = NULL; +#endif + + if (impl->operation_mode != OPERATION_MODE_BROWSE || + impl->current_folder == NULL) + return; + + op = g_new0 (struct file_trash_operation, 1); + op->impl = g_object_ref (impl); + op->target_folder = g_object_ref (impl->current_folder); + + current_selection_selected_foreach (impl, + trash_selected_file_foreach_cb, + op); + + if (op->files == NULL) + { + file_trash_operation_free (op); + return; + } + + op->files = g_slist_reverse (op->files); + +#if GLIB_CHECK_VERSION (2, 32, 0) + thread = g_thread_new ("gtk-filechooser-trash", + file_trash_operation_thread, + op); + g_thread_unref (thread); +#else + if (!g_thread_create (file_trash_operation_thread, op, FALSE, &error)) + { + error_message (impl, + _("Could not move files to the trash"), + error ? error->message : _("Unknown error")); + + if (error) + g_error_free (error); + + file_trash_operation_free (op); + } +#endif +} + +static void +trash_files_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + trash_selected_files (impl); +} + +static void +open_containing_folder_for_file (GtkFileChooserDefault *impl, + GFile *file) +{ + GFile *parent; + + if (file == NULL) + return; + + parent = g_file_get_parent (file); + if (parent == NULL) + return; + + change_folder_and_display_error (impl, parent, FALSE); + g_object_unref (parent); +} + +static void +open_file_list_containing_folder_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + GFile *file; + + if (impl->operation_mode != OPERATION_MODE_RECENT || + current_selection_count_selected_rows (impl) != 1) + return; + + file = get_selected_file (impl); + open_containing_folder_for_file (impl, file); + + if (file) + g_object_unref (file); +} + +static void +open_shortcut_containing_folder_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + GtkTreeIter iter; + ShortcutType shortcut_type; + gpointer col_data = NULL; + + if (!shortcuts_get_selected (impl, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, + SHORTCUTS_COL_DATA, &col_data, + SHORTCUTS_COL_TYPE, &shortcut_type, + -1); + + if (shortcut_type == SHORTCUT_TYPE_FILE && col_data != NULL) + open_containing_folder_for_file (impl, G_FILE (col_data)); +} + +struct copy_locations_closure +{ + GString *locations; + gboolean first; +}; + +static void +copy_location_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct copy_locations_closure *closure = data; + GFile *file; + gchar *location; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + if (file == NULL) + return; + + location = g_file_get_path (file); + + if (location == NULL) + location = g_file_get_uri (file); + + if (location) + { + if (!closure->first) + g_string_append_c (closure->locations, '\n'); + + g_string_append (closure->locations, location); + closure->first = FALSE; + g_free (location); + } + + g_object_unref (file); +} + +/* Callback used when the "Copy Location" menu item is activated */ +static void +copy_location_cb (GtkMenuItem *item, + GtkFileChooserDefault *impl) +{ + struct copy_locations_closure closure; + + closure.locations = g_string_new (NULL); + closure.first = TRUE; + + current_selection_selected_foreach (impl, + copy_location_foreach_cb, + &closure); + + if (closure.locations->len > 0) + { + GtkClipboard *clipboard; + + clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (impl->browse_files_current_view), + GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, closure.locations->str, -1); + } + + g_string_free (closure.locations, TRUE); +} + +struct preview_panel_update_closure +{ + GFile *file; + GFileInfo *info; + guint count; +}; + +static void +preview_panel_update_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + struct preview_panel_update_closure *closure = data; + GFileInfo *info; + + closure->count++; + + if (closure->count != 1) + return; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &closure->file, + -1); + + if (GTK_IS_FILE_SYSTEM_MODEL (model)) + { + info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (model), iter); + if (info) + closure->info = g_file_info_dup (info); + } +} + +static gchar * +preview_panel_format_size (GFileInfo *info) +{ + if (info == NULL || + g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY || + !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) + return g_strdup (_("Not calculated")); + +#if GLIB_CHECK_VERSION(2,30,0) + return g_format_size (g_file_info_get_size (info)); +#else + return g_format_size_for_display (g_file_info_get_size (info)); +#endif +} + +static gchar * +preview_panel_format_modified_time (GFileInfo *info) +{ + GDateTime *date_time; + gchar *formatted; + guint64 secs; + + if (info == NULL || + !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) + return g_strdup (_("Unknown")); + + secs = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + if (secs == 0) + return g_strdup (_("Unknown")); + + date_time = g_date_time_new_from_unix_local ((gint64) secs); + if (date_time == NULL) + return g_strdup (_("Unknown")); + + formatted = g_date_time_format (date_time, "%c"); + g_date_time_unref (date_time); + + return formatted ? formatted : g_strdup (_("Unknown")); +} + +static gchar * +preview_panel_get_type_name (GFileInfo *info) +{ + const gchar *content_type; + gchar *description; + + if (info == NULL) + return g_strdup (_("Unknown")); + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + return g_strdup (_("Folder")); + + content_type = g_file_info_get_content_type (info); + if (content_type == NULL) + return g_strdup (_("Unknown")); + + description = g_content_type_get_description (content_type); + return description ? description : g_strdup (content_type); +} + +static gint +preview_panel_get_image_size (GtkFileChooserDefault *impl) +{ + GtkAllocation allocation; + gint width; + gint list_height; + gint limit; + + if (impl->preview_box) + gtk_widget_get_allocation (impl->preview_box, &allocation); + else if (impl->preview_panel) + gtk_widget_get_allocation (impl->preview_panel, &allocation); + else + return PREVIEW_PANEL_WIDTH - 12; + + width = allocation.width; + if (width <= 1) + width = PREVIEW_PANEL_WIDTH; + + /* + * Use the file list height as the vertical limit. Do not use the + * preview panel height here: the preview image contributes to that + * height, which can create a resize feedback loop. + */ + list_height = 0; + if (impl->browse_files_scrolled_window) + { + gtk_widget_get_allocation (impl->browse_files_scrolled_window, &allocation); + list_height = allocation.height; + } + + limit = width - 12; + + if (list_height > 180) + limit = MIN (limit, list_height - 160); + else + limit = MIN (limit, PREVIEW_PANEL_WIDTH - 12); + + return CLAMP (limit, + PREVIEW_PANEL_IMAGE_MIN_SIZE, + PREVIEW_PANEL_IMAGE_MAX_SIZE); +} + +static void +preview_panel_clear_image (GtkFileChooserDefault *impl) +{ + if (!impl->preview_panel_image) + return; + + gtk_image_clear (GTK_IMAGE (impl->preview_panel_image)); + gtk_widget_hide (impl->preview_panel_image); + impl->preview_panel_last_image_size = 0; +} + +static GdkPixbuf * +preview_panel_scale_pixbuf_to_fit (GdkPixbuf *pixbuf, + gint size) +{ + GdkPixbuf *scaled; + gint width; + gint height; + gint scaled_width; + gint scaled_height; + gdouble scale; + + if (pixbuf == NULL) + return NULL; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + if (width <= 0 || height <= 0) + return pixbuf; + + scale = MIN ((gdouble) size / width, (gdouble) size / height); + scaled_width = MAX (1, (gint) (width * scale + 0.5)); + scaled_height = MAX (1, (gint) (height * scale + 0.5)); + + if (scaled_width == width && scaled_height == height) + return pixbuf; + + scaled = gdk_pixbuf_scale_simple (pixbuf, + scaled_width, + scaled_height, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + + return scaled; +} + +static GdkPixbuf * +preview_panel_render_icon (GtkFileChooserDefault *impl, + GFileInfo *info, + gint image_size) +{ + GtkIconTheme *theme; + GtkIconInfo *icon_info = NULL; + GdkPixbuf *pixbuf = NULL; + GIcon *icon; + GError *error = NULL; + + if (info == NULL) + return NULL; + + if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ICON)) + return NULL; + + icon = g_file_info_get_icon (info); + if (icon == NULL) + return NULL; + + theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + +#ifdef GTK_ICON_LOOKUP_FORCE_SVG + icon_info = gtk_icon_theme_lookup_by_gicon (theme, icon, image_size, + GTK_ICON_LOOKUP_FORCE_SVG); +#endif + + if (icon_info == NULL) + icon_info = gtk_icon_theme_lookup_by_gicon (theme, icon, image_size, 0); + + if (icon_info) + { + pixbuf = gtk_icon_info_load_icon (icon_info, &error); + gtk_icon_info_free (icon_info); + + if (error) + { + g_error_free (error); + error = NULL; + } + } + + if (pixbuf == NULL) + pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (impl), image_size); + + return preview_panel_scale_pixbuf_to_fit (pixbuf, image_size); +} + +static void +preview_panel_update_image (GtkFileChooserDefault *impl, + GFile *file, + GFileInfo *info) +{ + const gchar *content_type; + gchar *path; + GdkPixbuf *pixbuf = NULL; + GError *error = NULL; + gint image_size; + + if (!impl->preview_panel_image) + return; + + if (!file || !info) + { + preview_panel_clear_image (impl); + return; + } + + image_size = preview_panel_get_image_size (impl); + impl->preview_panel_last_image_size = image_size; + + content_type = g_file_info_get_content_type (info); + + if (content_type && g_str_has_prefix (content_type, "image/")) + { + path = g_file_get_path (file); + if (path) + { + pixbuf = gdk_pixbuf_new_from_file_at_scale (path, + image_size, + image_size, + TRUE, + &error); + g_free (path); + } + + if (error) + { + g_error_free (error); + error = NULL; + } + + pixbuf = preview_panel_scale_pixbuf_to_fit (pixbuf, image_size); + } + + if (pixbuf == NULL) + pixbuf = preview_panel_render_icon (impl, info, image_size); + + if (pixbuf == NULL) + { + preview_panel_clear_image (impl); + return; + } + + gtk_image_clear (GTK_IMAGE (impl->preview_panel_image)); + gtk_widget_queue_draw (impl->preview_panel_image); + + gtk_widget_set_size_request (impl->preview_panel_image, -1, -1); + gtk_image_set_from_pixbuf (GTK_IMAGE (impl->preview_panel_image), pixbuf); + gtk_widget_show (impl->preview_panel_image); + gtk_widget_queue_resize (impl->preview_panel_image); + gtk_widget_queue_draw (impl->preview_panel); + g_object_unref (pixbuf); +} + + +static void +update_preview_panel (GtkFileChooserDefault *impl) +{ + struct preview_panel_update_closure closure; + GFile *parent_file; + gchar *name; + gchar *escaped_name; + gchar *title_markup; + gchar *location; + gchar *type_name; + gchar *size; + gchar *modified; + gchar *details; + + if (!impl->preview_panel_title_label || !impl->preview_panel_info_label) + return; + + if (!impl->preview_panel_active) + return; + + closure.file = NULL; + closure.info = NULL; + closure.count = 0; + + if (current_selection_count_selected_rows (impl) == 1) + current_selection_selected_foreach (impl, + preview_panel_update_foreach_cb, + &closure); + + if (closure.count != 1 || closure.file == NULL) + { + gtk_label_set_text (GTK_LABEL (impl->preview_panel_title_label), + _("No file selected")); + gtk_label_set_text (GTK_LABEL (impl->preview_panel_info_label), ""); + preview_panel_clear_image (impl); + + if (closure.file) + g_object_unref (closure.file); + + if (closure.info) + g_object_unref (closure.info); + + return; + } + + if (closure.info && g_file_info_get_display_name (closure.info)) + name = g_strdup (g_file_info_get_display_name (closure.info)); + else + name = g_file_get_basename (closure.file); + + escaped_name = g_markup_escape_text (name ? name : _("Unknown"), -1); + title_markup = g_strdup_printf ("%s", escaped_name); + gtk_label_set_markup (GTK_LABEL (impl->preview_panel_title_label), title_markup); + + preview_panel_update_image (impl, closure.file, closure.info); + + parent_file = g_file_get_parent (closure.file); + if (parent_file) + { + location = g_file_get_parse_name (parent_file); + g_object_unref (parent_file); + } + else + location = g_file_get_parse_name (closure.file); + + type_name = preview_panel_get_type_name (closure.info); + size = preview_panel_format_size (closure.info); + modified = preview_panel_format_modified_time (closure.info); + + details = g_strdup_printf (_("Name: %s\nType: %s\nSize: %s\nModified: %s\nLocation: %s"), + name ? name : _("Unknown"), + type_name ? type_name : _("Unknown"), + size ? size : _("Unknown"), + modified ? modified : _("Unknown"), + location ? location : _("Unknown")); + + gtk_label_set_text (GTK_LABEL (impl->preview_panel_info_label), details); + + g_free (details); + g_free (modified); + g_free (size); + g_free (type_name); + g_free (location); + g_free (title_markup); + g_free (escaped_name); + g_free (name); + + g_object_unref (closure.file); + + if (closure.info) + g_object_unref (closure.info); +} + +static gboolean +preview_panel_update_timeout_cb (gpointer data) +{ + GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); + + impl->preview_panel_update_timeout_id = 0; + + update_preview_panel (impl); + + return FALSE; +} + +static void +preview_panel_queue_update (GtkFileChooserDefault *impl) +{ + if (!impl->preview_panel_active) + return; + + if (impl->preview_panel_update_timeout_id) + g_source_remove (impl->preview_panel_update_timeout_id); + + impl->preview_panel_update_timeout_id = + gdk_threads_add_timeout (75, preview_panel_update_timeout_cb, impl); +} + +static gboolean +preview_panel_resize_timeout_cb (gpointer data) +{ + GtkFileChooserDefault *impl = data; + gint image_size; + + impl->preview_panel_resize_timeout_id = 0; + + if (impl->preview_panel_update_timeout_id) + { + g_source_remove (impl->preview_panel_update_timeout_id); + impl->preview_panel_update_timeout_id = 0; + } + + if (!impl->preview_panel_active || !impl->preview_panel_image) + return FALSE; + + image_size = preview_panel_get_image_size (impl); + if (image_size == impl->preview_panel_last_image_size) + return FALSE; + + update_preview_panel (impl); + + return FALSE; +} + +static void +preview_panel_queue_resize_update (GtkFileChooserDefault *impl) +{ + if (!impl->preview_panel_active) + return; + + if (impl->preview_panel_resize_timeout_id) + g_source_remove (impl->preview_panel_resize_timeout_id); + + impl->preview_panel_resize_timeout_id = + gdk_threads_add_timeout (35, preview_panel_resize_timeout_cb, impl); +} + +static void +preview_panel_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + GtkFileChooserDefault *impl) +{ + gint image_size; + + if (!impl->preview_panel_active) + return; + + image_size = preview_panel_get_image_size (impl); + + if (ABS (image_size - impl->preview_panel_last_image_size) >= 8) + preview_panel_queue_resize_update (impl); +} + + +static void preview_panel_toggle_button_toggled_cb (GtkToggleButton *button, + GtkFileChooserDefault *impl); +static void show_preview_panel_toggled_cb (GtkCheckMenuItem *item, + GtkFileChooserDefault *impl); + +static void +preview_panel_set_active (GtkFileChooserDefault *impl, + gboolean active) +{ + impl->preview_panel_active = active; + + if (!active && impl->preview_panel_update_timeout_id) + { + g_source_remove (impl->preview_panel_update_timeout_id); + impl->preview_panel_update_timeout_id = 0; + } + + if (!active && impl->preview_panel_resize_timeout_id) + { + g_source_remove (impl->preview_panel_resize_timeout_id); + impl->preview_panel_resize_timeout_id = 0; + } + + if (active) + update_preview_panel (impl); + + update_preview_widget_visibility (impl); + + if (impl->preview_panel_toggle_button) + { + g_signal_handlers_block_by_func (impl->preview_panel_toggle_button, + G_CALLBACK (preview_panel_toggle_button_toggled_cb), impl); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (impl->preview_panel_toggle_button), + active); + g_signal_handlers_unblock_by_func (impl->preview_panel_toggle_button, + G_CALLBACK (preview_panel_toggle_button_toggled_cb), impl); + } + + if (impl->browse_files_popup_menu_preview_panel_item) + { + g_signal_handlers_block_by_func (impl->browse_files_popup_menu_preview_panel_item, + G_CALLBACK (show_preview_panel_toggled_cb), impl); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (impl->browse_files_popup_menu_preview_panel_item), + active); + g_signal_handlers_unblock_by_func (impl->browse_files_popup_menu_preview_panel_item, + G_CALLBACK (show_preview_panel_toggled_cb), impl); + } +} + +static void +preview_panel_toggle_button_toggled_cb (GtkToggleButton *button, + GtkFileChooserDefault *impl) +{ + preview_panel_set_active (impl, gtk_toggle_button_get_active (button)); +} + +static void +show_preview_panel_toggled_cb (GtkCheckMenuItem *item, + GtkFileChooserDefault *impl) +{ + preview_panel_set_active (impl, gtk_check_menu_item_get_active (item)); +} + + +/* Callback used when the "Show Thumbnails" menu item is toggled */ +static void +show_thumbnails_toggled_cb (GtkCheckMenuItem *item, + GtkFileChooserDefault *impl) +{ + gboolean show_thumbnails; + + show_thumbnails = gtk_check_menu_item_get_active (item); + if (impl->show_thumbnails == show_thumbnails) + return; + + impl->show_thumbnails = show_thumbnails; + + thumbnail_queue_clear (impl); + thumbnail_file_list_clear_pixbuf_caches (impl); +} + /* Callback used when the "Show Hidden Files" menu item is toggled */ static void show_hidden_toggled_cb (GtkCheckMenuItem *item, @@ -4071,7 +7082,7 @@ sort_ascending_toggled_cb (GtkCheckMenuItem *item, // The sort column is explicitly set to mtime for the recent model // This prevents it from switching when changing sort order - if (impl->view_mode == VIEW_MODE_ICON && impl->current_model == GTK_TREE_MODEL (impl->recent_model)) + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode) && impl->current_model == GTK_TREE_MODEL (impl->recent_model)) gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (impl->current_model), &impl->sort_column, NULL); impl->sort_order = GTK_SORT_ASCENDING; @@ -4100,7 +7111,7 @@ sort_descending_toggled_cb (GtkCheckMenuItem *item, // The sort column is explicitly set to mtime for the recent model // This prevents it from switching when changing sort order - if (impl->view_mode == VIEW_MODE_ICON && impl->current_model == GTK_TREE_MODEL (impl->recent_model)) + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode) && impl->current_model == GTK_TREE_MODEL (impl->recent_model)) gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (impl->current_model), &impl->sort_column, NULL); impl->sort_order = GTK_SORT_DESCENDING; @@ -4196,6 +7207,61 @@ out: g_object_unref (cancellable); } +static GFile * +file_list_get_drop_target_folder (GtkFileChooserDefault *impl, + GtkWidget *widget, + gint x, + gint y) +{ + GtkTreeModel *model = NULL; + GtkTreePath *path = NULL; + GtkTreeIter iter; + GFile *file = NULL; + gboolean is_folder = FALSE; + gboolean is_sensitive = FALSE; + + if (widget == impl->browse_files_tree_view) + { + if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), x, y, + &path, NULL, NULL, NULL)) + return NULL; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); + } + else if (widget == impl->browse_files_icon_view) + { + path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (widget), x, y); + if (!path) + return NULL; + + model = gtk_icon_view_get_model (GTK_ICON_VIEW (widget)); + } + else + return NULL; + + if (!model || !gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_tree_path_free (path); + return NULL; + } + + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &file, + MODEL_COL_IS_FOLDER, &is_folder, + MODEL_COL_IS_SENSITIVE, &is_sensitive, + -1); + + gtk_tree_path_free (path); + + if (file && is_folder && is_sensitive) + return file; + + if (file) + g_object_unref (file); + + return NULL; +} + static void file_list_drag_data_received_cb (GtkWidget *widget, GdkDragContext *context, @@ -4210,18 +7276,57 @@ file_list_drag_data_received_cb (GtkWidget *widget, gchar **uris; char *uri; GFile *file; + struct file_clipboard_paste_data *paste_data; + GFile *target_folder = NULL; + gboolean internal_drag = FALSE; impl = GTK_FILE_CHOOSER_DEFAULT (data); - /* Allow only drags from other widgets; see bug #533891. */ + /* + * Historically, bug #533891 disabled drops from the file list into itself. + * Keep that protection for drops onto empty space or regular files, but + * allow the useful case of dropping selected files onto a real folder. + */ if (gtk_drag_get_source_widget (context) == widget) { - g_signal_stop_emission_by_name (widget, "drag-data-received"); - return; + internal_drag = TRUE; + target_folder = file_list_get_drop_target_folder (impl, widget, x, y); + + if (!target_folder) + { + g_signal_stop_emission_by_name (widget, "drag-data-received"); + return; + } } /* Parse the text/uri-list string, navigate to the first one */ uris = gtk_selection_data_get_uris (selection_data); + if (uris && uris[0] && + impl->operation_mode == OPERATION_MODE_BROWSE && + impl->current_folder) + { + paste_data = file_clipboard_paste_data_from_uris (uris); + if (paste_data) + { + if (!target_folder) + target_folder = file_list_get_drop_target_folder (impl, widget, x, y); + + if (internal_drag) + paste_data->cut = TRUE; + + file_paste_operation_start (impl, paste_data, + target_folder ? target_folder : impl->current_folder); + if (target_folder) + g_object_unref (target_folder); + file_clipboard_paste_data_free (paste_data); + + g_strfreev (uris); + g_signal_stop_emission_by_name (widget, "drag-data-received"); + gtk_drag_finish (context, TRUE, FALSE, time_); + return; + } + } + if (uris && uris[0]) { struct FileListDragData *data; @@ -4275,6 +7380,77 @@ file_list_drag_motion_cb (GtkWidget *widget, } /* Constructs the popup menu for the file list if needed */ +#define FILE_LIST_DRAG_URIS_KEY "gtk-file-chooser-drag-uris" +#define FILE_LIST_DRAG_ACTIVE_KEY "gtk-file-chooser-drag-active" +#define FILE_LIST_DRAG_STARTED_KEY "gtk-file-chooser-drag-started" +#define FILE_LIST_DRAG_START_X_KEY "gtk-file-chooser-drag-start-x" +#define FILE_LIST_DRAG_START_Y_KEY "gtk-file-chooser-drag-start-y" + +static void +file_list_drag_uri_foreach_cb (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GPtrArray *uris = data; + GFile *file = NULL; + gchar *uri; + + gtk_tree_model_get (model, iter, + MODEL_COL_FILE, &file, + -1); + + if (!file) + return; + + uri = g_file_get_uri (file); + if (uri) + g_ptr_array_add (uris, uri); + + g_object_unref (file); +} + +static void +file_list_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time_, + GtkFileChooserDefault *impl) +{ + gchar **stored_uris; + GPtrArray *uris; + + stored_uris = g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_URIS_KEY); + if (stored_uris && + g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY)) + { + gtk_selection_data_set_uris (selection_data, stored_uris); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_STARTED_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_URIS_KEY, NULL); + return; + } + + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_STARTED_KEY, NULL); + + uris = g_ptr_array_new (); + + current_selection_selected_foreach (impl, + file_list_drag_uri_foreach_cb, + uris); + + if (uris->len > 0) + { + g_ptr_array_add (uris, NULL); + gtk_selection_data_set_uris (selection_data, (gchar **) uris->pdata); + } + + g_ptr_array_foreach (uris, (GFunc) g_free, NULL); + g_ptr_array_free (uris, TRUE); +} + static void file_list_build_popup_menu (GtkFileChooserDefault *impl) { @@ -4297,6 +7473,64 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL); + impl->browse_files_popup_menu_copy_item = item; + g_signal_connect (item, "activate", + G_CALLBACK (copy_files_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL); + impl->browse_files_popup_menu_cut_item = item; + g_signal_connect (item, "activate", + G_CALLBACK (cut_files_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PASTE, NULL); + impl->browse_files_popup_menu_paste_item = item; + g_signal_connect (item, "activate", + G_CALLBACK (paste_files_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Copy Location")); + impl->browse_files_popup_menu_copy_location_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (copy_location_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("Open _Containing Folder")); + impl->browse_files_popup_menu_open_containing_folder_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (open_file_list_containing_folder_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Rename...")); + impl->browse_files_popup_menu_rename_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (rename_file_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("Move to _Trash")); + impl->browse_files_popup_menu_trash_item = item; + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_stock (GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU)); + g_signal_connect (item, "activate", + G_CALLBACK (trash_files_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + item = gtk_separator_menu_item_new (); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); @@ -4308,6 +7542,21 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + item = gtk_check_menu_item_new_with_mnemonic (_("Show _Preview Panel")); + impl->browse_files_popup_menu_preview_panel_item = item; + g_signal_connect (item, "toggled", + G_CALLBACK (show_preview_panel_toggled_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + item = gtk_check_menu_item_new_with_mnemonic (_("Show _Thumbnails")); + impl->browse_files_popup_menu_thumbnails_item = item; + g_signal_connect (item, "toggled", + G_CALLBACK (show_thumbnails_toggled_cb), impl); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); + + if (impl->view_mode == VIEW_MODE_LIST) { item = gtk_check_menu_item_new_with_mnemonic (_("Show _Size Column")); @@ -4317,7 +7566,7 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (impl->browse_files_popup_menu), item); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { GtkWidget *menu, *subitem; @@ -4382,22 +7631,92 @@ file_list_build_popup_menu (GtkFileChooserDefault *impl) static void file_list_update_popup_menu (GtkFileChooserDefault *impl) { + gint file_count; + file_list_build_popup_menu (impl); + selection_check (impl, &file_count, NULL, NULL); + /* FIXME - handle OPERATION_MODE_SEARCH and OPERATION_MODE_RECENT */ /* The sensitivity of the Add to Bookmarks item is set in * bookmarks_check_add_sensitivity() */ + if (impl->browse_files_popup_menu_copy_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_copy_item, + file_count > 0); + + if (impl->browse_files_popup_menu_cut_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_cut_item, + impl->operation_mode == OPERATION_MODE_BROWSE && + file_count > 0); + + if (impl->browse_files_popup_menu_paste_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_paste_item, + impl->operation_mode == OPERATION_MODE_BROWSE && + impl->current_folder != NULL && + file_clipboard_has_file_targets (impl)); + + if (impl->browse_files_popup_menu_copy_location_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_copy_location_item, + file_count > 0); + + if (impl->browse_files_popup_menu_open_containing_folder_item) + { + gboolean show_item; + + show_item = impl->operation_mode == OPERATION_MODE_RECENT; + + if (show_item) + gtk_widget_show (impl->browse_files_popup_menu_open_containing_folder_item); + else + gtk_widget_hide (impl->browse_files_popup_menu_open_containing_folder_item); + + gtk_widget_set_sensitive (impl->browse_files_popup_menu_open_containing_folder_item, + show_item && + file_count == 1); + } + + if (impl->browse_files_popup_menu_rename_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_rename_item, + impl->operation_mode == OPERATION_MODE_BROWSE && + file_count == 1); + + if (impl->browse_files_popup_menu_trash_item) + gtk_widget_set_sensitive (impl->browse_files_popup_menu_trash_item, + impl->operation_mode == OPERATION_MODE_BROWSE && + file_count > 0); + /* 'Show Hidden Files' */ g_signal_handlers_block_by_func (impl->browse_files_popup_menu_hidden_files_item, G_CALLBACK (show_hidden_toggled_cb), impl); + + if (impl->browse_files_popup_menu_preview_panel_item) + { + g_signal_handlers_block_by_func (impl->browse_files_popup_menu_preview_panel_item, + G_CALLBACK (show_preview_panel_toggled_cb), impl); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (impl->browse_files_popup_menu_preview_panel_item), + impl->preview_panel_active); + g_signal_handlers_unblock_by_func (impl->browse_files_popup_menu_preview_panel_item, + G_CALLBACK (show_preview_panel_toggled_cb), impl); + } + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (impl->browse_files_popup_menu_hidden_files_item), impl->show_hidden); g_signal_handlers_unblock_by_func (impl->browse_files_popup_menu_hidden_files_item, G_CALLBACK (show_hidden_toggled_cb), impl); + if (impl->browse_files_popup_menu_thumbnails_item) + { + g_signal_handlers_block_by_func (impl->browse_files_popup_menu_thumbnails_item, + G_CALLBACK (show_thumbnails_toggled_cb), impl); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (impl->browse_files_popup_menu_thumbnails_item), + impl->show_thumbnails); + g_signal_handlers_unblock_by_func (impl->browse_files_popup_menu_thumbnails_item, + G_CALLBACK (show_thumbnails_toggled_cb), impl); + } + if (impl->view_mode == VIEW_MODE_LIST) { /* 'Show Size Column' */ @@ -4408,7 +7727,7 @@ file_list_update_popup_menu (GtkFileChooserDefault *impl) g_signal_handlers_unblock_by_func (impl->browse_files_popup_menu_size_column_item, G_CALLBACK (show_size_column_toggled_cb), impl); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { gint column = impl->sort_column; GtkSortType order = impl->sort_order; @@ -4487,6 +7806,251 @@ popup_position_func (GtkMenu *menu, *push_in = FALSE; } +static gboolean +browse_files_select_tree_item_at_pos (GtkFileChooserDefault *impl, + gint x, + gint y) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (impl->browse_files_tree_view); + + if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, + &path, NULL, NULL, NULL)) + return FALSE; + + selection = gtk_tree_view_get_selection (tree_view); + + if (!gtk_tree_selection_path_is_selected (selection, path)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } + + gtk_tree_path_free (path); + + return TRUE; +} + +static gboolean +browse_files_select_icon_item_at_pos (GtkFileChooserDefault *impl, + gint x, + gint y) +{ + GtkIconView *icon_view; + GtkTreePath *path; + + icon_view = GTK_ICON_VIEW (impl->browse_files_icon_view); + path = gtk_icon_view_get_path_at_pos (icon_view, x, y); + + if (path == NULL) + return FALSE; + + if (!gtk_icon_view_path_is_selected (icon_view, path)) + { + gtk_icon_view_unselect_all (icon_view); + gtk_icon_view_select_path (icon_view, path); + } + + gtk_icon_view_set_cursor (icon_view, path, NULL, FALSE); + gtk_tree_path_free (path); + + return TRUE; +} + + +static gboolean +browse_files_clicked_item_is_selected (GtkFileChooserDefault *impl, + GtkWidget *widget, + GdkEventButton *event) +{ + GtkTreePath *path; + gboolean selected = FALSE; + + if (widget == impl->browse_files_tree_view) + { + GtkTreeSelection *selection; + + if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (impl->browse_files_tree_view), + (gint) event->x, (gint) event->y, + &path, NULL, NULL, NULL)) + return FALSE; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + selected = gtk_tree_selection_path_is_selected (selection, path); + gtk_tree_path_free (path); + + return selected; + } + + if (widget == impl->browse_files_icon_view) + { + path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (impl->browse_files_icon_view), + (gint) event->x, (gint) event->y); + if (!path) + return FALSE; + + selected = gtk_icon_view_path_is_selected (GTK_ICON_VIEW (impl->browse_files_icon_view), path); + gtk_tree_path_free (path); + + return selected; + } + + return FALSE; +} + +static void +file_list_store_drag_uris (GtkWidget *widget, + GtkFileChooserDefault *impl) +{ + GPtrArray *uris; + gchar **stored_uris; + + uris = g_ptr_array_new (); + + current_selection_selected_foreach (impl, + file_list_drag_uri_foreach_cb, + uris); + + if (uris->len == 0) + { + g_ptr_array_free (uris, TRUE); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_URIS_KEY, NULL); + return; + } + + g_ptr_array_add (uris, NULL); + stored_uris = (gchar **) g_ptr_array_free (uris, FALSE); + + g_object_set_data_full (G_OBJECT (widget), + FILE_LIST_DRAG_URIS_KEY, + stored_uris, + (GDestroyNotify) g_strfreev); +} + +static void +file_list_clear_drag_state (GtkWidget *widget) +{ + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_STARTED_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_START_X_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_START_Y_KEY, NULL); + g_object_set_data (G_OBJECT (widget), FILE_LIST_DRAG_URIS_KEY, NULL); +} + +static gboolean +file_list_maybe_store_drag_uris (GtkWidget *widget, + GdkEventButton *event, + GtkFileChooserDefault *impl) +{ + if (event->button != 1 || + current_selection_count_selected_rows (impl) <= 1 || + !browse_files_clicked_item_is_selected (impl, widget, event)) + { + file_list_clear_drag_state (widget); + return FALSE; + } + + file_list_store_drag_uris (widget, impl); + if (!g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_URIS_KEY)) + return FALSE; + + g_object_set_data (G_OBJECT (widget), + FILE_LIST_DRAG_ACTIVE_KEY, + GINT_TO_POINTER (1)); + g_object_set_data (G_OBJECT (widget), + FILE_LIST_DRAG_START_X_KEY, + GINT_TO_POINTER ((gint) event->x)); + g_object_set_data (G_OBJECT (widget), + FILE_LIST_DRAG_START_Y_KEY, + GINT_TO_POINTER ((gint) event->y)); + + return TRUE; +} + +static gboolean +file_list_multi_drag_motion_cb (GtkWidget *widget, + GdkEventMotion *event, + GtkFileChooserDefault *impl) +{ + gint start_x; + gint start_y; + GtkTargetList *target_list; + + if (!g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY)) + return FALSE; + + if (!(event->state & GDK_BUTTON1_MASK)) + { + file_list_clear_drag_state (widget); + return FALSE; + } + + if (g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_STARTED_KEY)) + return TRUE; + + start_x = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + FILE_LIST_DRAG_START_X_KEY)); + start_y = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), + FILE_LIST_DRAG_START_Y_KEY)); + + if (!gtk_drag_check_threshold (widget, start_x, start_y, + (gint) event->x, (gint) event->y)) + return TRUE; + + g_object_set_data (G_OBJECT (widget), + FILE_LIST_DRAG_STARTED_KEY, + GINT_TO_POINTER (1)); + + target_list = gtk_target_list_new (NULL, 0); + gtk_target_list_add_uri_targets (target_list, 0); + + gtk_drag_begin (widget, + target_list, + GDK_ACTION_COPY | GDK_ACTION_MOVE, + 1, + (GdkEvent *) event); + + gtk_target_list_unref (target_list); + + return TRUE; +} + +static gboolean +file_list_multi_drag_button_release_cb (GtkWidget *widget, + GdkEventButton *event, + GtkFileChooserDefault *impl) +{ + if (event->button == 1 && + g_object_get_data (G_OBJECT (widget), FILE_LIST_DRAG_ACTIVE_KEY)) + { + file_list_clear_drag_state (widget); + return TRUE; + } + + return FALSE; +} + +static gboolean +browse_files_select_clicked_item (GtkFileChooserDefault *impl, + GtkWidget *widget, + GdkEventButton *event) +{ + if (widget == impl->browse_files_tree_view) + return browse_files_select_tree_item_at_pos (impl, + (gint) event->x, + (gint) event->y); + + if (widget == impl->browse_files_icon_view) + return browse_files_select_icon_item_at_pos (impl, + (gint) event->x, + (gint) event->y); + + return FALSE; +} + static void file_list_popup_menu (GtkFileChooserDefault *impl, GdkEventButton *event) @@ -4530,12 +8094,18 @@ list_button_press_event_cb (GtkWidget *widget, if (in_press) return FALSE; + if (file_list_maybe_store_drag_uris (widget, event, impl)) + return TRUE; + if (!_gtk_button_event_triggers_context_menu (event)) return FALSE; - in_press = TRUE; - gtk_widget_event (widget, (GdkEvent *) event); - in_press = FALSE; + if (!browse_files_select_clicked_item (impl, widget, event)) + { + in_press = TRUE; + gtk_widget_event (widget, (GdkEvent *) event); + in_press = FALSE; + } file_list_popup_menu (impl, event); return TRUE; @@ -4555,6 +8125,118 @@ file_list_set_sort_column_ids (GtkFileChooserDefault *impl) } } +static gboolean file_name_needs_tooltip (GtkFileChooserDefault *impl, + const gchar *name, + gint available_width); + +static void +file_list_tooltip_cache_clear (GtkFileChooserDefault *impl) +{ + if (impl->list_tooltip_path) + { + gtk_tree_path_free (impl->list_tooltip_path); + impl->list_tooltip_path = NULL; + } + + impl->list_tooltip_model = NULL; + + g_free (impl->list_tooltip_text); + impl->list_tooltip_text = NULL; + impl->list_tooltip_width = 0; + impl->list_tooltip_visible = FALSE; +} + +static gint +file_list_get_tooltip_width (GtkFileChooserDefault *impl) +{ + GtkCellRenderer *text_renderer = NULL; + GList *cells; + GList *l; + gint width = 0; + gint start_pos = 0; + gint text_xpad = 0; + + if (!impl->list_name_column) + return 0; + + cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (impl->list_name_column)); + + for (l = cells; l; l = l->next) + { + if (GTK_IS_CELL_RENDERER_TEXT (l->data)) + { + text_renderer = GTK_CELL_RENDERER (l->data); + break; + } + } + + if (text_renderer && + gtk_tree_view_column_cell_get_position (impl->list_name_column, + text_renderer, + &start_pos, + &width)) + { + gtk_cell_renderer_get_padding (text_renderer, &text_xpad, NULL); + width -= text_xpad * 2; + + /* Match GtkCellRendererText's practical ellipsize threshold more + * closely. gtk_tree_view_column_cell_get_position() plus renderer + * padding is still slightly wider than the text area where the + * renderer starts drawing an ellipsis. + */ + width -= 2; + } + else + { + gint icon_size; + + width = gtk_tree_view_column_get_width (impl->list_name_column); + + icon_size = impl->list_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; + + width -= icon_size + 16; + } + + g_list_free (cells); + + return MAX (1, width); +} + +static gboolean +file_list_tooltip_cache_matches (GtkFileChooserDefault *impl, + GtkTreeModel *model, + GtkTreePath *path, + gint available_width) +{ + return impl->list_tooltip_path && + impl->list_tooltip_model == model && + gtk_tree_path_compare (impl->list_tooltip_path, path) == 0 && + impl->list_tooltip_width == available_width && + impl->list_tooltip_operation_mode == impl->operation_mode; +} + +static void +file_list_tooltip_cache_set (GtkFileChooserDefault *impl, + GtkTreeModel *model, + GtkTreePath *path, + gint available_width, + gboolean visible, + const gchar *tooltip_text) +{ + file_list_tooltip_cache_clear (impl); + + impl->list_tooltip_path = gtk_tree_path_copy (path); + impl->list_tooltip_model = model; + impl->list_tooltip_width = available_width; + impl->list_tooltip_operation_mode = impl->operation_mode; + impl->list_tooltip_visible = visible; + + if (tooltip_text) + impl->list_tooltip_text = g_strdup (tooltip_text); +} + static gboolean file_list_query_tooltip_cb (GtkWidget *widget, gint x, @@ -4564,40 +8246,310 @@ file_list_query_tooltip_cb (GtkWidget *widget, gpointer user_data) { GtkFileChooserDefault *impl = user_data; + GtkTreeView *tree_view; + GtkTreeViewColumn *column = NULL; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; - GFile *file; - char *parse_name; + gchar *display_name; + gchar *tooltip_text; + gint available_width; - if (impl->operation_mode == OPERATION_MODE_BROWSE) + tree_view = GTK_TREE_VIEW (impl->browse_files_tree_view); + + if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, NULL, &column, NULL, NULL)) return FALSE; + if (column != impl->list_name_column) + return FALSE; - if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (impl->browse_files_tree_view), + if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_tip, &model, &path, &iter)) return FALSE; + available_width = file_list_get_tooltip_width (impl); + + if (file_list_tooltip_cache_matches (impl, model, path, available_width)) + { + if (!impl->list_tooltip_visible) + { + gtk_tree_path_free (path); + return FALSE; + } + + gtk_tooltip_set_text (tooltip, impl->list_tooltip_text); + gtk_tree_view_set_tooltip_row (tree_view, tooltip, path); + gtk_tree_path_free (path); + return TRUE; + } + + if (impl->operation_mode != OPERATION_MODE_BROWSE) + { + GFile *file = NULL; + + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (!file) + { + file_list_tooltip_cache_set (impl, model, path, available_width, FALSE, NULL); + gtk_tree_path_free (path); + return FALSE; + } + + tooltip_text = g_file_get_parse_name (file); + g_object_unref (file); + + file_list_tooltip_cache_set (impl, model, path, available_width, TRUE, tooltip_text); + + gtk_tooltip_set_text (tooltip, tooltip_text); + gtk_tree_view_set_tooltip_row (tree_view, tooltip, path); + + g_free (tooltip_text); + gtk_tree_path_free (path); + + return TRUE; + } + gtk_tree_model_get (model, &iter, - MODEL_COL_FILE, &file, + MODEL_COL_NAME, &display_name, -1); - if (file == NULL) + if (!file_name_needs_tooltip (impl, display_name, available_width)) { + file_list_tooltip_cache_set (impl, model, path, available_width, FALSE, NULL); + g_free (display_name); gtk_tree_path_free (path); return FALSE; } - parse_name = g_file_get_parse_name (file); - gtk_tooltip_set_text (tooltip, parse_name); - gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (impl->browse_files_tree_view), - tooltip, - path); + tooltip_text = display_name; - g_free (parse_name); - g_object_unref (file); + file_list_tooltip_cache_set (impl, model, path, available_width, TRUE, tooltip_text); + + gtk_tooltip_set_text (tooltip, tooltip_text); + gtk_tree_view_set_tooltip_row (tree_view, tooltip, path); + + g_free (tooltip_text); + gtk_tree_path_free (path); + + return TRUE; +} + + +static void +file_icon_view_tooltip_cache_clear (GtkFileChooserDefault *impl) +{ + if (impl->icon_tooltip_path) + { + gtk_tree_path_free (impl->icon_tooltip_path); + impl->icon_tooltip_path = NULL; + } + + g_free (impl->icon_tooltip_text); + impl->icon_tooltip_text = NULL; + impl->icon_tooltip_width = 0; + impl->icon_tooltip_visible = FALSE; +} + +static gboolean +file_name_needs_tooltip (GtkFileChooserDefault *impl, + const gchar *name, + gint available_width) +{ + PangoLayout *layout; + gboolean ellipsized; + + if (name == NULL || available_width <= 0) + return FALSE; + + layout = gtk_widget_create_pango_layout (impl->browse_files_current_view, name); + pango_layout_set_width (layout, available_width * PANGO_SCALE); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); + + ellipsized = pango_layout_is_ellipsized (layout); + + g_object_unref (layout); + + return ellipsized; +} + +static gint +file_icon_view_get_tooltip_width (GtkFileChooserDefault *impl) +{ + gint text_xpad = 0; + gint width; + + if (impl->list_name_renderer) + gtk_cell_renderer_get_padding (impl->list_name_renderer, &text_xpad, NULL); + + if (impl->view_mode == VIEW_MODE_COMPACT) + { + gint icon_size; + gint icon_xpad = 0; + gint icon_cell_width; + + icon_size = impl->list_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; + + if (impl->list_icon_renderer) + gtk_cell_renderer_get_padding (impl->list_icon_renderer, &icon_xpad, NULL); + + icon_cell_width = icon_xpad * 2 + icon_size; + width = COMPACT_VIEW_ITEM_WIDTH - icon_cell_width - COMPACT_VIEW_SPACING; + } + else if (impl->view_mode == VIEW_MODE_ICON) + { + gint icon_size; + gint icon_xpad = 0; + gint item_width; + + icon_size = impl->icon_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_ICON_VIEW_ICON_SIZE; + + if (impl->list_icon_renderer) + gtk_cell_renderer_get_padding (impl->list_icon_renderer, &icon_xpad, NULL); + + item_width = MAX (ICON_VIEW_ITEM_WIDTH, icon_size + icon_xpad * 2); + width = item_width; + } + else + return 0; + + width -= text_xpad * 2; + + return MAX (1, width); +} + +static gboolean +file_icon_view_tooltip_cache_matches (GtkFileChooserDefault *impl, + GtkTreePath *path, + gint available_width) +{ + return impl->icon_tooltip_path && + gtk_tree_path_compare (impl->icon_tooltip_path, path) == 0 && + impl->icon_tooltip_width == available_width && + impl->icon_tooltip_view_mode == impl->view_mode && + impl->icon_tooltip_operation_mode == impl->operation_mode; +} + +static void +file_icon_view_tooltip_cache_set (GtkFileChooserDefault *impl, + GtkTreePath *path, + gint available_width, + gboolean visible, + const gchar *tooltip_text) +{ + file_icon_view_tooltip_cache_clear (impl); + + impl->icon_tooltip_path = gtk_tree_path_copy (path); + impl->icon_tooltip_width = available_width; + impl->icon_tooltip_view_mode = impl->view_mode; + impl->icon_tooltip_operation_mode = impl->operation_mode; + impl->icon_tooltip_visible = visible; + + if (tooltip_text) + impl->icon_tooltip_text = g_strdup (tooltip_text); +} + +static gboolean +file_icon_view_query_tooltip_cb (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_tip, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkFileChooserDefault *impl = user_data; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + gint available_width; + gchar *display_name; + gchar *tooltip_text; + + if (!gtk_icon_view_get_tooltip_context (GTK_ICON_VIEW (impl->browse_files_icon_view), + &x, &y, + keyboard_tip, + &model, &path, &iter)) + return FALSE; + + available_width = file_icon_view_get_tooltip_width (impl); + + if (file_icon_view_tooltip_cache_matches (impl, path, available_width)) + { + if (!impl->icon_tooltip_visible) + { + gtk_tree_path_free (path); + return FALSE; + } + + gtk_tooltip_set_text (tooltip, impl->icon_tooltip_text); + gtk_icon_view_set_tooltip_item (GTK_ICON_VIEW (impl->browse_files_icon_view), + tooltip, + path); + gtk_tree_path_free (path); + return TRUE; + } + + if (impl->operation_mode != OPERATION_MODE_BROWSE) + { + GFile *file = NULL; + + gtk_tree_model_get (model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (!file) + { + file_icon_view_tooltip_cache_set (impl, path, available_width, FALSE, NULL); + gtk_tree_path_free (path); + return FALSE; + } + + tooltip_text = g_file_get_parse_name (file); + g_object_unref (file); + + file_icon_view_tooltip_cache_set (impl, path, available_width, TRUE, tooltip_text); + + gtk_tooltip_set_text (tooltip, tooltip_text); + gtk_icon_view_set_tooltip_item (GTK_ICON_VIEW (impl->browse_files_icon_view), + tooltip, + path); + + g_free (tooltip_text); + gtk_tree_path_free (path); + + return TRUE; + } + + gtk_tree_model_get (model, &iter, + MODEL_COL_NAME, &display_name, + -1); + + if (!file_name_needs_tooltip (impl, display_name, available_width)) + { + file_icon_view_tooltip_cache_set (impl, path, available_width, FALSE, NULL); + g_free (display_name); + gtk_tree_path_free (path); + return FALSE; + } + + tooltip_text = display_name; + + file_icon_view_tooltip_cache_set (impl, path, available_width, TRUE, tooltip_text); + + gtk_tooltip_set_text (tooltip, tooltip_text); + gtk_icon_view_set_tooltip_item (GTK_ICON_VIEW (impl->browse_files_icon_view), + tooltip, + path); + + g_free (tooltip_text); gtk_tree_path_free (path); return TRUE; @@ -4611,7 +8563,7 @@ set_icon_cell_renderer_fixed_size (GtkFileChooserDefault *impl, int icon_size; gint xpad, ypad; - if (view_mode == VIEW_MODE_LIST) + if (view_mode == VIEW_MODE_LIST || view_mode == VIEW_MODE_COMPACT) { icon_size = impl->list_view_icon_size; @@ -4671,8 +8623,60 @@ update_icon_view_cell_layout (GtkFileChooserDefault *impl) if (!impl->browse_files_icon_view) return; + file_icon_view_tooltip_cache_clear (impl); + + if (impl->view_mode == VIEW_MODE_COMPACT) + { + GtkIconView *icon_view; + gint icon_cell_width; + gint text_width; + + icon_view = GTK_ICON_VIEW (impl->browse_files_icon_view); + item_width = COMPACT_VIEW_ITEM_WIDTH; + + gtk_icon_view_set_item_orientation (icon_view, GTK_ORIENTATION_HORIZONTAL); + gtk_icon_view_set_item_width (icon_view, item_width); + gtk_icon_view_set_spacing (icon_view, COMPACT_VIEW_SPACING); + gtk_icon_view_set_row_spacing (icon_view, COMPACT_VIEW_ROW_SPACING); + gtk_icon_view_set_column_spacing (icon_view, COMPACT_VIEW_COLUMN_SPACING); + gtk_icon_view_set_margin (icon_view, COMPACT_VIEW_MARGIN); + + icon_size = impl->list_view_icon_size; + if (icon_size <= 0) + icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; + + icon_cell_width = icon_size; + + if (impl->list_icon_renderer) + { + gtk_cell_renderer_get_padding (impl->list_icon_renderer, &xpad, &ypad); + icon_cell_width = xpad * 2 + icon_size; + gtk_cell_renderer_set_fixed_size (impl->list_icon_renderer, + icon_cell_width, + ypad * 2 + icon_size); + } + + if (impl->list_name_renderer) + { + text_width = MAX (1, item_width - icon_cell_width - COMPACT_VIEW_SPACING); + + g_object_set (G_OBJECT (impl->list_name_renderer), + "alignment", PANGO_ALIGN_LEFT, + "ellipsize", PANGO_ELLIPSIZE_END, + "wrap-width", -1, + "yalign", 0.5f, + NULL); + gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (impl->list_name_renderer), + text_width, -1); + } + + return; + } + item_width = get_icon_view_item_width (impl); + gtk_icon_view_set_item_orientation (GTK_ICON_VIEW (impl->browse_files_icon_view), + GTK_ORIENTATION_VERTICAL); gtk_icon_view_set_item_width (GTK_ICON_VIEW (impl->browse_files_icon_view), item_width); @@ -4698,7 +8702,11 @@ update_icon_view_cell_layout (GtkFileChooserDefault *impl) if (impl->list_name_renderer) { g_object_set (G_OBJECT (impl->list_name_renderer), + "alignment", PANGO_ALIGN_CENTER, + "ellipsize", PANGO_ELLIPSIZE_END, + "wrap-mode", PANGO_WRAP_WORD_CHAR, "wrap-width", item_width, + "yalign", 0.0f, NULL); gtk_cell_renderer_set_fixed_size (GTK_CELL_RENDERER (impl->list_name_renderer), item_width, -1); @@ -4759,6 +8767,15 @@ create_browse_files_tree_view (GtkFileChooserDefault *impl) NULL, 0, GDK_ACTION_COPY | GDK_ACTION_MOVE); gtk_drag_source_add_uri_targets (impl->browse_files_tree_view); + g_signal_connect (impl->browse_files_tree_view, "drag-data-get", + G_CALLBACK (file_list_drag_data_get_cb), impl); + g_signal_connect (impl->browse_files_tree_view, "motion-notify-event", + G_CALLBACK (file_list_multi_drag_motion_cb), impl); + g_signal_connect (impl->browse_files_tree_view, "button-release-event", + G_CALLBACK (file_list_multi_drag_button_release_cb), impl); + + gtk_widget_add_events (impl->browse_files_tree_view, + GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK); g_signal_connect (selection, "changed", G_CALLBACK (list_selection_changed), impl); @@ -4844,6 +8861,12 @@ create_browse_files_icon_view (GtkFileChooserDefault *impl) gtk_widget_add_events (impl->browse_files_icon_view, GDK_SCROLL_MASK); g_signal_connect (impl->browse_files_icon_view, "scroll-event", G_CALLBACK (browse_files_icon_view_scroll_event_cb), impl); + g_signal_connect (impl->browse_files_icon_view, "size-allocate", + G_CALLBACK (icon_view_visible_range_size_allocate_cb), impl); + + g_object_set (impl->browse_files_icon_view, "has-tooltip", TRUE, NULL); + g_signal_connect (impl->browse_files_icon_view, "query-tooltip", + G_CALLBACK (file_icon_view_query_tooltip_cb), impl); gtk_drag_dest_set (impl->browse_files_icon_view, GTK_DEST_DEFAULT_ALL, @@ -4861,10 +8884,20 @@ create_browse_files_icon_view (GtkFileChooserDefault *impl) NULL, 0, GDK_ACTION_COPY | GDK_ACTION_MOVE); gtk_drag_source_add_uri_targets (impl->browse_files_icon_view); + g_signal_connect (impl->browse_files_icon_view, "drag-data-get", + G_CALLBACK (file_list_drag_data_get_cb), impl); + g_signal_connect (impl->browse_files_icon_view, "motion-notify-event", + G_CALLBACK (file_list_multi_drag_motion_cb), impl); + g_signal_connect (impl->browse_files_icon_view, "button-release-event", + G_CALLBACK (file_list_multi_drag_button_release_cb), impl); + + gtk_widget_add_events (impl->browse_files_icon_view, + GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK); impl->list_icon_renderer = gtk_cell_renderer_pixbuf_new (); g_object_set (G_OBJECT (impl->list_icon_renderer), "ypad", 3u, + "follow-state", TRUE, NULL); set_icon_cell_renderer_fixed_size (impl, GTK_CELL_RENDERER (impl->list_icon_renderer), @@ -4873,7 +8906,8 @@ create_browse_files_icon_view (GtkFileChooserDefault *impl) gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (impl->browse_files_icon_view), impl->list_icon_renderer, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (impl->browse_files_icon_view), - impl->list_icon_renderer, "pixbuf", MODEL_COL_ICON_PIXBUF); + impl->list_icon_renderer, "pixbuf", + impl->view_mode == VIEW_MODE_COMPACT ? MODEL_COL_LIST_PIXBUF : MODEL_COL_ICON_PIXBUF); impl->list_name_renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (impl->list_name_renderer), @@ -4881,6 +8915,11 @@ create_browse_files_icon_view (GtkFileChooserDefault *impl) "wrap-mode", PANGO_WRAP_WORD_CHAR, "yalign", 0.0f, NULL); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (impl->browse_files_icon_view), + impl->list_icon_renderer, + file_clipboard_icon_cell_data_func, + impl, + NULL); update_icon_view_cell_layout (impl); g_signal_connect (impl->list_name_renderer, "edited", @@ -4906,6 +8945,12 @@ create_file_list (GtkFileChooserDefault *impl) /* Scrolled window */ swin = gtk_scrolled_window_new (NULL, NULL); impl->browse_files_scrolled_window = swin; + g_signal_connect (gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (swin)), + "value-changed", + G_CALLBACK (icon_view_visible_range_adjustment_changed_cb), impl); + g_signal_connect (gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swin)), + "value-changed", + G_CALLBACK (icon_view_visible_range_adjustment_changed_cb), impl); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), @@ -4933,22 +8978,65 @@ file_pane_create (GtkFileChooserDefault *impl, vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); - /* Box for lists and preview */ + /* Paned box for file list and preview panel */ - hbox = gtk_hbox_new (FALSE, PREVIEW_HBOX_SPACING); + hbox = gtk_hpaned_new (); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); /* File list */ widget = create_file_list (impl); - gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0); + gtk_paned_pack1 (GTK_PANED (hbox), widget, TRUE, FALSE); gtk_size_group_add_widget (size_group, widget); /* Preview */ impl->preview_box = gtk_vbox_new (FALSE, 12); - gtk_box_pack_start (GTK_BOX (hbox), impl->preview_box, FALSE, FALSE, 0); + gtk_widget_set_size_request (impl->preview_box, PREVIEW_PANEL_WIDTH, -1); + gtk_paned_pack2 (GTK_PANED (hbox), impl->preview_box, FALSE, FALSE); + + impl->preview_panel = gtk_vbox_new (FALSE, 8); + gtk_widget_set_size_request (impl->preview_panel, PREVIEW_PANEL_WIDTH, -1); + gtk_container_set_border_width (GTK_CONTAINER (impl->preview_panel), 6); + g_signal_connect (impl->preview_panel, "size-allocate", + G_CALLBACK (preview_panel_size_allocate_cb), impl); + + + impl->preview_panel_image = gtk_image_new (); + gtk_widget_set_size_request (impl->preview_panel_image, -1, -1); + gtk_misc_set_alignment (GTK_MISC (impl->preview_panel_image), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (impl->preview_panel), + impl->preview_panel_image, FALSE, FALSE, 0); + + impl->preview_panel_title_label = gtk_label_new (_("No file selected")); + gtk_widget_set_size_request (impl->preview_panel_title_label, PREVIEW_PANEL_WIDTH, -1); + gtk_misc_set_alignment (GTK_MISC (impl->preview_panel_title_label), 0.0, 0.0); + gtk_label_set_selectable (GTK_LABEL (impl->preview_panel_title_label), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (impl->preview_panel_title_label), PANGO_ELLIPSIZE_MIDDLE); + gtk_box_pack_start (GTK_BOX (impl->preview_panel), + impl->preview_panel_title_label, FALSE, FALSE, 0); + + widget = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request (widget, -1, 2); + gtk_widget_show (widget); + gtk_box_pack_start (GTK_BOX (impl->preview_panel), widget, FALSE, FALSE, 0); + + impl->preview_panel_info_label = gtk_label_new (""); + gtk_widget_set_size_request (impl->preview_panel_info_label, PREVIEW_PANEL_WIDTH, -1); + gtk_misc_set_alignment (GTK_MISC (impl->preview_panel_info_label), 0.0, 0.0); + gtk_label_set_selectable (GTK_LABEL (impl->preview_panel_info_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (impl->preview_panel_info_label), TRUE); + gtk_label_set_width_chars (GTK_LABEL (impl->preview_panel_info_label), 28); + gtk_box_pack_start (GTK_BOX (impl->preview_panel), + impl->preview_panel_info_label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (impl->preview_box), + impl->preview_panel, FALSE, FALSE, 0); + gtk_widget_show_all (impl->preview_panel); + gtk_widget_hide (impl->preview_panel); + /* Don't show preview box initially */ /* Filter combo */ @@ -5167,6 +9255,61 @@ location_mode_set (GtkFileChooserDefault *impl, impl->location_mode = new_mode; } +static gboolean +location_select_current_folder_text_idle (gpointer data) +{ + GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); + + if (impl->location_entry && + (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) && + impl->location_mode == LOCATION_MODE_FILENAME_ENTRY) + gtk_editable_select_region (GTK_EDITABLE (impl->location_entry), 0, -1); + + return FALSE; +} + +static void +location_set_current_folder_text (GtkFileChooserDefault *impl, + gboolean select_text) +{ + gchar *path; + + if (!impl->location_entry || !impl->current_folder) + return; + + path = g_file_get_parse_name (impl->current_folder); + + if (!g_str_has_suffix (path, G_DIR_SEPARATOR_S)) + { + gchar *folder_path; + + folder_path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL); + g_free (path); + path = folder_path; + } + + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), path); + + if (select_text) + { + gtk_editable_select_region (GTK_EDITABLE (impl->location_entry), 0, -1); + + /* Folder changes from typed paths can trigger entry/completion updates + * after the text was set. Queue a final selection so keyboard-entered + * folders keep the full path selected. + */ + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + location_select_current_folder_text_idle, + g_object_ref (impl), + g_object_unref); + } + else + gtk_editable_set_position (GTK_EDITABLE (impl->location_entry), -1); + + g_free (path); +} + static void location_toggle_popup_handler (GtkFileChooserDefault *impl) { @@ -5184,6 +9327,7 @@ location_toggle_popup_handler (GtkFileChooserDefault *impl) if (impl->location_mode == LOCATION_MODE_PATH_BAR) { location_mode_set (impl, LOCATION_MODE_FILENAME_ENTRY, TRUE); + location_set_current_folder_text (impl, TRUE); } else if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY) { @@ -5198,31 +9342,205 @@ location_toggle_popup_handler (GtkFileChooserDefault *impl) } } +static void +view_mode_selected_files_free (GSList *files) +{ + g_slist_foreach (files, (GFunc) g_object_unref, NULL); + g_slist_free (files); +} + +static GSList * +view_mode_get_selected_files (GtkFileChooserDefault *impl, + ViewMode view_mode) +{ + GSList *files = NULL; + + if (!impl->current_model) + return NULL; + + if (view_mode == VIEW_MODE_LIST && impl->browse_files_tree_view) + { + GtkTreeSelection *selection; + GList *rows; + GList *l; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + rows = gtk_tree_selection_get_selected_rows (selection, NULL); + + for (l = rows; l; l = l->next) + { + GtkTreePath *path = l->data; + GtkTreeIter iter; + GFile *file = NULL; + + if (gtk_tree_model_get_iter (impl->current_model, &iter, path)) + gtk_tree_model_get (impl->current_model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (file) + files = g_slist_prepend (files, file); + } + + g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL); + g_list_free (rows); + } + else if (VIEW_MODE_USES_ICON_VIEW (view_mode) && impl->browse_files_icon_view) + { + GList *rows; + GList *l; + + rows = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (impl->browse_files_icon_view)); + + for (l = rows; l; l = l->next) + { + GtkTreePath *path = l->data; + GtkTreeIter iter; + GFile *file = NULL; + + if (gtk_tree_model_get_iter (impl->current_model, &iter, path)) + gtk_tree_model_get (impl->current_model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (file) + files = g_slist_prepend (files, file); + } + + g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL); + g_list_free (rows); + } + + return files; +} + +static gboolean +view_mode_file_is_selected (GSList *files, + GFile *file) +{ + GSList *l; + + if (!file) + return FALSE; + + for (l = files; l; l = l->next) + { + if (g_file_equal (G_FILE (l->data), file)) + return TRUE; + } + + return FALSE; +} + +static void +view_mode_restore_selected_files (GtkFileChooserDefault *impl, + GSList *files) +{ + GtkTreeIter iter; + GtkTreePath *first_path = NULL; + gboolean valid; + + if (!files || !impl->current_model) + return; + + current_selection_unselect_all (impl); + + valid = gtk_tree_model_get_iter_first (impl->current_model, &iter); + + while (valid) + { + GFile *file = NULL; + + gtk_tree_model_get (impl->current_model, &iter, + MODEL_COL_FILE, &file, + -1); + + if (view_mode_file_is_selected (files, file)) + { + GtkTreePath *path; + + path = gtk_tree_model_get_path (impl->current_model, &iter); + + if (impl->view_mode == VIEW_MODE_LIST) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + gtk_tree_selection_select_path (selection, path); + + if (!first_path) + gtk_tree_view_set_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), + path, NULL, FALSE); + } + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) + { + gtk_icon_view_select_path (GTK_ICON_VIEW (impl->browse_files_icon_view), path); + + if (!first_path) + gtk_icon_view_set_cursor (GTK_ICON_VIEW (impl->browse_files_icon_view), + path, NULL, FALSE); + } + else + { + gtk_tree_path_free (path); + g_assert_not_reached (); + } + + if (!first_path) + first_path = gtk_tree_path_copy (path); + + gtk_tree_path_free (path); + } + + if (file) + g_object_unref (file); + + valid = gtk_tree_model_iter_next (impl->current_model, &iter); + } + + if (first_path) + { + if (impl->view_mode == VIEW_MODE_LIST) + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->browse_files_tree_view), + first_path, NULL, FALSE, 0.0, 0.0); + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) + gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (impl->browse_files_icon_view), + first_path, FALSE, 0.0, 0.0); + + gtk_tree_path_free (first_path); + } +} + static void view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode) { - GtkWidget *old_view = NULL; + GtkWidget *old_view; ViewMode old_view_mode = impl->view_mode; + GSList *selected_files; if (old_view_mode == view_mode) return; + old_view = impl->browse_files_current_view; + selected_files = view_mode_get_selected_files (impl, old_view_mode); + + file_list_tooltip_cache_clear (impl); + file_icon_view_tooltip_cache_clear (impl); + impl->view_mode = view_mode; gtk_combo_box_set_active (GTK_COMBO_BOX (impl->view_mode_combo_box), view_mode); /* Creating the target view */ - if (view_mode == VIEW_MODE_ICON) + if (VIEW_MODE_USES_ICON_VIEW (view_mode)) { create_browse_files_icon_view (impl); impl->browse_files_current_view = impl->browse_files_icon_view; - old_view = impl->browse_files_tree_view; } else if (view_mode == VIEW_MODE_LIST) { create_browse_files_tree_view (impl); impl->browse_files_current_view = impl->browse_files_tree_view; - old_view = impl->browse_files_icon_view; } else g_assert_not_reached (); @@ -5230,31 +9548,35 @@ view_mode_set (GtkFileChooserDefault *impl, ViewMode view_mode) /* Set model and selection */ current_view_set_file_model (impl, impl->current_model); current_view_set_select_multiple (impl, impl->select_multiple); + view_mode_restore_selected_files (impl, selected_files); + view_mode_selected_files_free (selected_files); copy_old_selection_to_current_view (impl, old_view_mode); - /* Destroy the old view */ if (view_mode == VIEW_MODE_ICON) + gtk_widget_show (impl->icon_view_scale_hbox); + else + gtk_widget_hide (impl->icon_view_scale_hbox); + + if (old_view_mode == VIEW_MODE_LIST) { impl->browse_files_tree_view = NULL; impl->list_name_column = NULL; impl->list_mtime_column = NULL; impl->list_size_column = NULL; - gtk_widget_show (impl->icon_view_scale_hbox); } - else if (view_mode == VIEW_MODE_LIST) + else if (VIEW_MODE_USES_ICON_VIEW (old_view_mode) && + !VIEW_MODE_USES_ICON_VIEW (view_mode)) { impl->browse_files_icon_view = NULL; impl->list_icon_renderer = NULL; impl->list_name_renderer = NULL; - gtk_widget_hide (impl->icon_view_scale_hbox); } - else - g_assert_not_reached (); if (impl->browse_files_popup_menu) gtk_menu_detach (GTK_MENU (impl->browse_files_popup_menu)); - gtk_widget_destroy (old_view); + if (old_view) + gtk_widget_destroy (old_view); /* Display the new view */ gtk_container_add (GTK_CONTAINER (impl->browse_files_scrolled_window), @@ -5358,85 +9680,154 @@ icon_view_scale_queue_visible_after_layout_clear (GtkFileChooserDefault *impl) (GDestroyNotify) g_object_unref); } +static gboolean +file_list_get_visible_indices (GtkFileChooserDefault *impl, + gint *first, + gint *last) +{ + GtkTreePath *start, *end; + gint *start_indices, *end_indices; + gboolean valid; + + if (!impl->current_model || !GTK_IS_FILE_SYSTEM_MODEL (impl->current_model) || + !get_visible_range (&start, &end, impl)) + return FALSE; + + start_indices = gtk_tree_path_get_indices (start); + end_indices = gtk_tree_path_get_indices (end); + valid = start_indices && end_indices && end_indices[0] >= start_indices[0]; + + if (valid) + { + *first = start_indices[0]; + *last = end_indices[0]; + } + + gtk_tree_path_free (start); + gtk_tree_path_free (end); + return valid; +} + +#define FILE_LIST_VISIBLE_OVERSCAN(first, last) \ + CLAMP (MAX (1, (last) - (first) + 1) * 2, 16, 64) + +static void +file_list_clear_cache_range (GtkFileChooserDefault *impl, + gint first, + gint last, + gint column) +{ + GtkTreeModel *model = impl->current_model; + GtkTreePath *path; + GtkTreeIter iter; + gint i; + + first = MAX (0, first); + path = gtk_tree_path_new_from_indices (first, -1); + + for (i = first; i <= last; i++) + { + if (!gtk_tree_model_get_iter (model, &iter, path)) + break; + + _gtk_file_system_model_clear_cache_for_iter (GTK_FILE_SYSTEM_MODEL (model), + &iter, column); + gtk_tree_path_next (path); + } + + gtk_tree_path_free (path); +} + static void icon_view_scale_clear_visible_model_cache (GtkFileChooserDefault *impl, gboolean include_overscan) { - GtkTreeModel *tree_model; - GtkTreePath *start, *end, *path; - GtkTreeIter iter; - gint *start_indices; - gint *end_indices; - gint first; - gint last; - gint i; + gint first, last; - if (!impl->current_model || !GTK_IS_FILE_SYSTEM_MODEL (impl->current_model)) - return; - - /* - * A full _gtk_file_system_model_clear_cache() is O(total files). During - * icon-view zooming we refresh only the visible range. - * - * When shrinking icons, the new layout can make rows visible that were just - * outside the old visible range. Only in that case do we add a small margin. - */ - if (!get_visible_range (&start, &end, impl)) + if (!file_list_get_visible_indices (impl, &first, &last)) { - _gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model), - MODEL_COL_ICON_PIXBUF); - return; - } - - start_indices = gtk_tree_path_get_indices (start); - end_indices = gtk_tree_path_get_indices (end); - - if (!start_indices || !end_indices) - { - gtk_tree_path_free (start); - gtk_tree_path_free (end); - _gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model), - MODEL_COL_ICON_PIXBUF); + if (impl->current_model && GTK_IS_FILE_SYSTEM_MODEL (impl->current_model)) + _gtk_file_system_model_clear_cache (GTK_FILE_SYSTEM_MODEL (impl->current_model), + MODEL_COL_ICON_PIXBUF); return; } if (include_overscan) { - gint overscan; + gint overscan = + MIN (impl->icon_view_scale_visible_overscan > 0 + ? impl->icon_view_scale_visible_overscan : 16, + ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN); - overscan = impl->icon_view_scale_visible_overscan; - if (overscan <= 0) - overscan = 16; - - overscan = MIN (overscan, ICON_VIEW_SCALE_MAX_VISIBLE_OVERSCAN); - - first = MAX (0, start_indices[0] - overscan); - last = end_indices[0] + overscan; - } - else - { - first = start_indices[0]; - last = end_indices[0]; + first -= overscan; + last += overscan; } - tree_model = impl->current_model; - path = gtk_tree_path_new_from_indices (first, -1); + file_list_clear_cache_range (impl, first, last, MODEL_COL_ICON_PIXBUF); +} - for (i = first; i <= last; i++) - { - if (!gtk_tree_model_get_iter (tree_model, &iter, path)) - break; +/* Cache the GtkIconView viewport; per-cell visible-range scans are O(n^2). */ +static gboolean +icon_view_visible_range_refresh_cb (gpointer data) +{ + GtkFileChooserDefault *impl = data; + gint first, last; + gint overscan; - _gtk_file_system_model_clear_cache_for_iter (GTK_FILE_SYSTEM_MODEL (tree_model), - &iter, - MODEL_COL_ICON_PIXBUF); + impl->icon_view_visible_range_idle_id = 0; + impl->icon_view_visible_range_valid = FALSE; - gtk_tree_path_next (path); - } + if (!VIEW_MODE_USES_ICON_VIEW (impl->view_mode) || + !impl->browse_files_icon_view || + !file_list_get_visible_indices (impl, &first, &last)) + return FALSE; - gtk_tree_path_free (path); - gtk_tree_path_free (start); - gtk_tree_path_free (end); + impl->icon_view_visible_first = first; + impl->icon_view_visible_last = last; + impl->icon_view_visible_range_valid = TRUE; + + overscan = FILE_LIST_VISIBLE_OVERSCAN (first, last); + file_list_clear_cache_range + (impl, first - overscan, last + overscan, + impl->view_mode == VIEW_MODE_COMPACT + ? MODEL_COL_LIST_PIXBUF : MODEL_COL_ICON_PIXBUF); + gtk_widget_queue_draw (impl->browse_files_icon_view); + + return FALSE; +} + +static void +icon_view_visible_range_schedule (GtkFileChooserDefault *impl) +{ + impl->icon_view_visible_range_valid = FALSE; + + if (impl->icon_view_visible_range_idle_id || + !VIEW_MODE_USES_ICON_VIEW (impl->view_mode) || + !impl->browse_files_icon_view || + !impl->current_model) + return; + + /* Run after GtkIconView's layout and coalesce repeated requests. */ + impl->icon_view_visible_range_idle_id = + gdk_threads_add_idle_full (G_PRIORITY_LOW, + icon_view_visible_range_refresh_cb, + impl, + NULL); +} + +static void +icon_view_visible_range_size_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + GtkFileChooserDefault *impl) +{ + icon_view_visible_range_schedule (impl); +} + +static void +icon_view_visible_range_adjustment_changed_cb (GtkAdjustment *adjustment, + GtkFileChooserDefault *impl) +{ + icon_view_visible_range_schedule (impl); } static gboolean @@ -5516,6 +9907,9 @@ icon_view_scale_apply_visible_size_cb (gpointer data) */ impl->icon_view_icon_size = icon_size; + thumbnail_queue_clear (impl); + thumbnail_file_list_clear_pixbuf_caches (impl); + if (impl->view_mode != VIEW_MODE_ICON || !impl->browse_files_icon_view) return FALSE; @@ -5696,6 +10090,9 @@ location_button_toggled_cb (GtkToggleButton *toggle, } location_mode_set (impl, new_mode, FALSE); + + if (is_active) + location_set_current_folder_text (impl, TRUE); } /* Creates a combo box with two items: List View and Icon View. */ @@ -5707,6 +10104,8 @@ view_mode_combo_box_create (GtkFileChooserDefault *impl) _("List View")); /* VIEW_MODE_LIST */ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(impl->view_mode_combo_box), _("Icon View")); /* VIEW_MODE_ICON */ + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(impl->view_mode_combo_box), + _("Compact View")); /* VIEW_MODE_COMPACT */ gtk_combo_box_set_active (GTK_COMBO_BOX(impl->view_mode_combo_box), VIEW_MODE_LIST); @@ -5764,7 +10163,28 @@ icon_view_scale_create (GtkFileChooserDefault *impl) G_CALLBACK (icon_view_scale_value_changed_cb), impl); } -/* Creates a toggle button for the location entry. */ +/* Creates a toggle button for the preview panel. */ +static void +preview_panel_toggle_button_create (GtkFileChooserDefault *impl) +{ + GtkWidget *image; + char *str; + + impl->preview_panel_toggle_button = gtk_toggle_button_new (); + image = gtk_image_new_from_stock (GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (impl->preview_panel_toggle_button), image); + gtk_widget_show (image); + + g_signal_connect (impl->preview_panel_toggle_button, "toggled", + G_CALLBACK (preview_panel_toggle_button_toggled_cb), impl); + + str = _("Show Preview Panel"); + gtk_widget_set_tooltip_text (impl->preview_panel_toggle_button, str); + atk_object_set_name (gtk_widget_get_accessible (impl->preview_panel_toggle_button), str); + + gtk_widget_show (impl->preview_panel_toggle_button); +} + static void location_button_create (GtkFileChooserDefault *impl) { @@ -5781,7 +10201,7 @@ location_button_create (GtkFileChooserDefault *impl) g_signal_connect (impl->location_button, "toggled", G_CALLBACK (location_button_toggled_cb), impl); - str = _("Type a file name"); + str = _("Edit Location"); gtk_widget_set_tooltip_text (impl->location_button, str); atk_object_set_name (gtk_widget_get_accessible (impl->location_button), str); @@ -5910,6 +10330,10 @@ path_bar_widgets_create (GtkFileChooserDefault *impl) /* Icon view scale */ icon_view_scale_create (impl); + preview_panel_toggle_button_create (impl); + gtk_size_group_add_widget (impl->browse_path_bar_size_group, impl->preview_panel_toggle_button); + gtk_box_pack_end (GTK_BOX (impl->browse_path_bar_hbox), impl->preview_panel_toggle_button, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (impl->browse_path_bar_hbox), impl->icon_view_scale_hbox, FALSE, FALSE, 0); /* Create Folder */ @@ -6405,6 +10829,9 @@ operation_mode_set (GtkFileChooserDefault *impl, OperationMode mode) { ShortcutsIndex shortcut_to_select; + file_list_tooltip_cache_clear (impl); + file_icon_view_tooltip_cache_clear (impl); + operation_mode_stop (impl, impl->operation_mode); impl->operation_mode = mode; @@ -6962,6 +11389,7 @@ gtk_file_chooser_default_style_set (GtkWidget *widget, profile_start ("start", NULL); impl = GTK_FILE_CHOOSER_DEFAULT (widget); + thumbnail_standard_icon_cache_clear (impl); profile_msg (" parent class style_set start", NULL); GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->style_set (widget, previous_style); @@ -7018,6 +11446,7 @@ settings_load (GtkFileChooserDefault *impl) LocationMode location_mode; ViewMode view_mode; gboolean show_hidden; + gboolean show_thumbnails; gboolean show_size_column; gint sort_column, icon_view_scale; GtkSortType sort_order; @@ -7029,6 +11458,9 @@ settings_load (GtkFileChooserDefault *impl) view_mode = _gtk_file_chooser_settings_get_view_mode (settings); icon_view_scale = _gtk_file_chooser_settings_get_icon_view_scale (settings); show_hidden = _gtk_file_chooser_settings_get_show_hidden (settings); + preview_panel_set_active (impl, + _gtk_file_chooser_settings_get_show_preview_panel (settings)); + show_thumbnails = _gtk_file_chooser_settings_get_show_thumbnails (settings); show_size_column = _gtk_file_chooser_settings_get_show_size_column (settings); sort_column = _gtk_file_chooser_settings_get_sort_column (settings); sort_order = _gtk_file_chooser_settings_get_sort_order (settings); @@ -7055,6 +11487,7 @@ settings_load (GtkFileChooserDefault *impl) gtk_file_chooser_set_show_hidden (GTK_FILE_CHOOSER (impl), show_hidden); + impl->show_thumbnails = show_thumbnails; impl->show_size_column = show_size_column; if (impl->list_size_column) gtk_tree_view_column_set_visible (impl->list_size_column, show_size_column); @@ -7099,6 +11532,8 @@ settings_save (GtkFileChooserDefault *impl) _gtk_file_chooser_settings_set_view_mode (settings, impl->view_mode); _gtk_file_chooser_settings_set_icon_view_scale (settings, impl->icon_view_icon_size); _gtk_file_chooser_settings_set_show_hidden (settings, gtk_file_chooser_get_show_hidden (GTK_FILE_CHOOSER (impl))); + _gtk_file_chooser_settings_set_show_preview_panel (settings, impl->preview_panel_active); + _gtk_file_chooser_settings_set_show_thumbnails (settings, impl->show_thumbnails); _gtk_file_chooser_settings_set_show_size_column (settings, impl->show_size_column); _gtk_file_chooser_settings_set_sort_column (settings, impl->sort_column); _gtk_file_chooser_settings_set_sort_order (settings, impl->sort_order); @@ -7335,6 +11770,11 @@ load_set_model (GtkFileChooserDefault *impl) g_assert (impl->browse_files_model != NULL); profile_msg (" gtk_tree_view_set_model start", NULL); + /* FileChooser list rows have width-independent height. */ + if (impl->view_mode == VIEW_MODE_LIST) + _gtk_tree_view_set_fixed_height_mode_unchecked + (GTK_TREE_VIEW (impl->browse_files_tree_view)); + current_view_set_file_model (impl, GTK_TREE_MODEL (impl->browse_files_model)); if (impl->view_mode == VIEW_MODE_LIST) @@ -7352,61 +11792,8 @@ load_set_model (GtkFileChooserDefault *impl) profile_end ("end", NULL); } -/* Timeout callback used when the loading timer expires */ -static gboolean -load_timeout_cb (gpointer data) -{ - GtkFileChooserDefault *impl; - profile_start ("start", NULL); - impl = GTK_FILE_CHOOSER_DEFAULT (data); - g_assert (impl->load_state == LOAD_PRELOAD); - g_assert (impl->load_timeout_id != 0); - g_assert (impl->browse_files_model != NULL); - - impl->load_timeout_id = 0; - impl->load_state = LOAD_LOADING; - - load_set_model (impl); - - profile_end ("end", NULL); - - return FALSE; -} - -/* Sets up a new load timer for the model and switches to the LOAD_PRELOAD state */ -static void -load_setup_timer (GtkFileChooserDefault *impl) -{ - g_assert (impl->load_timeout_id == 0); - g_assert (impl->load_state != LOAD_PRELOAD); - - impl->load_timeout_id = gdk_threads_add_timeout (MAX_LOADING_TIME, load_timeout_cb, impl); - impl->load_state = LOAD_PRELOAD; -} - -/* Removes the load timeout; changes the impl->load_state to the specified value. */ -static void -load_remove_timer (GtkFileChooserDefault *impl, LoadState new_load_state) -{ - if (impl->load_timeout_id != 0) - { - g_assert (impl->load_state == LOAD_PRELOAD); - - g_source_remove (impl->load_timeout_id); - impl->load_timeout_id = 0; - } - else - g_assert (impl->load_state == LOAD_EMPTY || - impl->load_state == LOAD_LOADING || - impl->load_state == LOAD_FINISHED); - - g_assert (new_load_state == LOAD_EMPTY || - new_load_state == LOAD_LOADING || - new_load_state == LOAD_FINISHED); - impl->load_state = new_load_state; -} /* Selects the first row in the file list */ static void @@ -7452,7 +11839,7 @@ center_selected_row_foreach_cb (GtkTreeModel *model, if (closure->impl->view_mode == VIEW_MODE_LIST) gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (closure->impl->browse_files_tree_view), path, NULL, TRUE, 0.5, 0.0); - else if (closure->impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (closure->impl->view_mode)) gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (closure->impl->browse_files_icon_view), path, TRUE, 0.5, 0.0); else g_assert_not_reached (); @@ -7625,15 +12012,8 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel *model, if (error) show_error_on_reading_current_folder (impl, error); - if (impl->load_state == LOAD_PRELOAD) - { - load_remove_timer (impl, LOAD_FINISHED); - load_set_model (impl); - } - else if (impl->load_state == LOAD_LOADING) - { - /* Nothing */ - } + if (impl->load_state == LOAD_LOADING) + load_set_model (impl); else { /* We can't g_assert_not_reached(), as something other than us may have @@ -7643,10 +12023,10 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel *model, return; } - g_assert (impl->load_timeout_id == 0); - impl->load_state = LOAD_FINISHED; + thumbnail_file_list_clear_pixbuf_caches (impl); + pending_select_files_process (impl); set_busy_cursor (impl, FALSE); #ifdef PROFILE_FILE_CHOOSER @@ -7660,7 +12040,7 @@ static void stop_loading_and_clear_list_model (GtkFileChooserDefault *impl, gboolean remove_from_treeview) { - load_remove_timer (impl, LOAD_EMPTY); + impl->load_state = LOAD_EMPTY; if (impl->browse_files_model) { @@ -7828,25 +12208,155 @@ file_system_model_got_thumbnail (GObject *object, GAsyncResult *res, gpointer da info = g_file_info_dup (_gtk_file_system_model_get_info (model, &iter)); - copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); - copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); + thumbnail_debug_log_file ("gio-info", + file, + "queried-path=%s current-path=%s generated-size=%d generated=%d", + g_file_info_get_attribute_byte_string (queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH) ? + g_file_info_get_attribute_byte_string (queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH) : "(none)", + g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH) ? + g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH) : "(none)", + g_file_info_get_attribute_int32 (info, FILECHOOSER_GENERATED_THUMBNAIL_SIZE), + g_file_info_get_attribute_object (info, FILECHOOSER_GENERATED_THUMBNAIL) != NULL); + + { + const gchar *current_thumbnail_path; + const gchar *queried_thumbnail_path; + + current_thumbnail_path = g_file_info_get_attribute_byte_string (info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + queried_thumbnail_path = g_file_info_get_attribute_byte_string (queried, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + + /* GIO may report an already-existing larger freedesktop thumbnail. + * Keep it, but never replace an existing better path with a smaller one. + */ + if (queried_thumbnail_path && + (!current_thumbnail_path || + thumbnail_cache_max_dimension_for_path (queried_thumbnail_path) >= + thumbnail_cache_max_dimension_for_path (current_thumbnail_path))) + copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + } + copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_SIZE); + copy_attribute (info, queried, G_FILE_ATTRIBUTE_TIME_MODIFIED); _gtk_file_system_model_update_file (model, file, info); - g_object_unref (info); g_object_unref (queried); GDK_THREADS_LEAVE (); } +static GdkPixbuf * +file_system_model_scale_pixbuf_to_fit (GdkPixbuf *pixbuf, + gint size) +{ + GdkPixbuf *scaled; + gint width; + gint height; + gint scaled_width; + gint scaled_height; + gdouble scale; + + if (pixbuf == NULL) + return NULL; + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + if (width <= 0 || height <= 0) + return pixbuf; + + if (width <= size && height <= size) + return pixbuf; + + scale = MIN ((gdouble) size / width, (gdouble) size / height); + scaled_width = MAX (1, (gint) (width * scale + 0.5)); + scaled_height = MAX (1, (gint) (height * scale + 0.5)); + + scaled = gdk_pixbuf_scale_simple (pixbuf, + scaled_width, + scaled_height, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + + return scaled; +} + +static GdkPixbuf * +file_system_model_render_icon (GtkFileChooserDefault *impl, + GFileInfo *info, + gint icon_size) +{ + GObject *thumbnail; + gint thumbnail_size; + GFileInfo *icon_info; + GdkPixbuf *pixbuf; + + if (impl->show_thumbnails) + { + thumbnail = g_file_info_get_attribute_object (info, + FILECHOOSER_GENERATED_THUMBNAIL); + thumbnail_size = g_file_info_get_attribute_int32 (info, + FILECHOOSER_GENERATED_THUMBNAIL_SIZE); + + if (thumbnail && GDK_IS_PIXBUF (thumbnail) && thumbnail_size >= icon_size) + return file_system_model_scale_pixbuf_to_fit (GDK_PIXBUF (g_object_ref (thumbnail)), + icon_size); + } + + if (impl->show_thumbnails && + g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)) + { + pixbuf = thumbnail_standard_icon_cache_get (impl, info, icon_size); + + if (thumbnail_debug_enabled ()) + g_debug ("GtkFileChooser thumbnail: render-standard-icon: icon-size=%d pixbuf=%dx%d", + icon_size, + pixbuf ? gdk_pixbuf_get_width (pixbuf) : 0, + pixbuf ? gdk_pixbuf_get_height (pixbuf) : 0); + + return pixbuf; + } + + if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH)) + { + const gchar *thumbnail_path; + + thumbnail_path = g_file_info_get_attribute_byte_string (info, + G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (impl), icon_size); + + if (thumbnail_debug_enabled ()) + g_debug ("GtkFileChooser thumbnail: render-path: icon-size=%d path=%s path-dim=%d pixbuf=%dx%d", + icon_size, + thumbnail_path ? thumbnail_path : "(none)", + thumbnail_cache_max_dimension_for_path (thumbnail_path), + pixbuf ? gdk_pixbuf_get_width (pixbuf) : 0, + pixbuf ? gdk_pixbuf_get_height (pixbuf) : 0); + + return pixbuf; + } + + icon_info = g_file_info_dup (info); + g_file_info_remove_attribute (icon_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + + pixbuf = _gtk_file_info_render_icon (icon_info, GTK_WIDGET (impl), icon_size); + + g_object_unref (icon_info); + + return pixbuf; +} + static gboolean get_visible_range (GtkTreePath **start, GtkTreePath **end, GtkFileChooserDefault *impl) { if (impl->view_mode == VIEW_MODE_LIST) return gtk_tree_view_get_visible_range (GTK_TREE_VIEW (impl->browse_files_tree_view), start, end); - if (impl->view_mode == VIEW_MODE_ICON) + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) return gtk_icon_view_get_visible_range (GTK_ICON_VIEW (impl->browse_files_icon_view), start, end); g_assert_not_reached (); } @@ -7908,14 +12418,35 @@ file_system_model_set (GtkFileSystemModel *model, else g_value_set_boolean (value, TRUE); break; + case MODEL_COL_IS_HIDDEN: + if (info) + { + gboolean is_hidden = FALSE; + gboolean is_backup = FALSE; + + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) + is_hidden = g_file_info_get_is_hidden (info); + + if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP)) + is_backup = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP); + + g_value_set_boolean (value, is_hidden || is_backup); + } + else + g_value_set_boolean (value, FALSE); + break; case MODEL_COL_LIST_PIXBUF: case MODEL_COL_ICON_PIXBUF: if (info) { GtkTreeModel *tree_model; - GtkTreePath *path, *start, *end; + GtkTreePath *path; GtkTreeIter iter; - gboolean file_visible; + gboolean file_near_visible; + gint path_index; + gint start_index; + gint end_index; + gint overscan_items; /* not loading icon view's icon in the list view */ if (column == MODEL_COL_ICON_PIXBUF && impl->view_mode == VIEW_MODE_LIST) @@ -7925,6 +12456,40 @@ file_system_model_set (GtkFileSystemModel *model, if (tree_model != GTK_TREE_MODEL (model)) return FALSE; + file_near_visible = FALSE; + start_index = -1; + end_index = -1; + + if (impl->view_mode == VIEW_MODE_LIST) + file_list_get_visible_indices (impl, &start_index, &end_index); + else if (impl->icon_view_visible_range_valid) + { + start_index = impl->icon_view_visible_first; + end_index = impl->icon_view_visible_last; + } + else + { + /* Wait for valid GtkIconView geometry. */ + icon_view_visible_range_schedule (impl); + } + + if (start_index >= 0 && end_index >= start_index) + { + if (!_gtk_file_system_model_get_iter_for_file (model, + &iter, + file)) + g_assert_not_reached (); + + path = gtk_tree_model_get_path (tree_model, &iter); + path_index = gtk_tree_path_get_indices (path)[0]; + overscan_items = FILE_LIST_VISIBLE_OVERSCAN (start_index, end_index); + + file_near_visible = (path_index >= start_index - overscan_items && + path_index <= end_index + overscan_items); + + gtk_tree_path_free (path); + } + /* #1 use standard icon if it is loaded */ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_ICON)) { @@ -7945,28 +12510,16 @@ file_system_model_set (GtkFileSystemModel *model, icon_size = FALLBACK_LIST_VIEW_ICON_SIZE; } - g_value_take_object (value, _gtk_file_info_render_icon (info, GTK_WIDGET (impl), icon_size)); + thumbnail_queue_enqueue (impl, file, info, file_near_visible); + + g_value_take_object (value, file_system_model_render_icon (impl, info, icon_size)); return TRUE; } - if (!get_visible_range (&start, &end, impl)) - return FALSE; - - if (!_gtk_file_system_model_get_iter_for_file (model, - &iter, - file)) - g_assert_not_reached (); - - path = gtk_tree_model_get_path (tree_model, &iter); - file_visible = (gtk_tree_path_compare (start, path) != 1 && - gtk_tree_path_compare (path, end) != 1); - - gtk_tree_path_free (path); - gtk_tree_path_free (start); - gtk_tree_path_free (end); - - if (file_visible) + if (file_near_visible) { + thumbnail_queue_enqueue (impl, file, info, file_near_visible); + /* #2 start loading standard icon (callback will be handled by #1) */ if (!g_file_info_has_attribute (info, "filechooser::icon_queried")) { @@ -7974,7 +12527,10 @@ file_system_model_set (GtkFileSystemModel *model, g_file_query_info_async (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "," - G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_ATTRIBUTE_STANDARD_ICON "," + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, _gtk_file_system_model_get_cancellable (model), @@ -8069,6 +12625,8 @@ set_list_model (GtkFileChooserDefault *impl, profile_start ("start", NULL); + thumbnail_queue_clear (impl); + stop_loading_and_clear_list_model (impl, TRUE); set_busy_cursor (impl, TRUE); @@ -8087,12 +12645,15 @@ set_list_model (GtkFileChooserDefault *impl, gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), MODEL_COL_SIZE, size_sort_func, impl, NULL); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), MODEL_COL_MTIME, mtime_sort_func, impl, NULL); gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (impl->browse_files_model), NULL, NULL, NULL); - set_sort_column (impl); + gtk_tree_sortable_set_sort_column_id + (GTK_TREE_SORTABLE (impl->browse_files_model), + impl->sort_column, + impl->sort_order); impl->list_sort_ascending = TRUE; g_signal_connect (impl->browse_files_model, "sort-column-changed", G_CALLBACK (list_sort_column_changed_cb), impl); - load_setup_timer (impl); /* This changes the state to LOAD_PRELOAD */ + impl->load_state = LOAD_LOADING; g_signal_connect (impl->browse_files_model, "finished-loading", G_CALLBACK (browse_files_model_finished_loading_cb), impl); @@ -8465,7 +13026,11 @@ update_current_folder_get_info_cb (GCancellable *cancellable, _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->current_folder); - if (data->clear_entry) + if ((impl->action == GTK_FILE_CHOOSER_ACTION_OPEN || + impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) && + impl->location_mode == LOCATION_MODE_FILENAME_ENTRY) + location_set_current_folder_text (impl, data->clear_entry); + else if (data->clear_entry) gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); } @@ -9407,7 +13972,7 @@ get_selected_file_info_from_file_list (GtkFileChooserDefault *impl, } *had_selection = TRUE; } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { if (!get_selected_tree_iter_from_icon_view (impl, &iter)) { @@ -9849,11 +14414,29 @@ paste_text_received (GtkClipboard *clipboard, static void location_popup_on_paste_handler (GtkFileChooserDefault *impl) { - GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), - GDK_SELECTION_CLIPBOARD); + GtkWidget *toplevel; + GtkWidget *current_focus; + GtkClipboard *clipboard; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); + + if (GTK_IS_WINDOW (toplevel)) + { + current_focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); + + if (current_focus == impl->browse_files_tree_view || + current_focus == impl->browse_files_icon_view) + { + paste_files_cb (NULL, impl); + return; + } + } + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (impl), + GDK_SELECTION_CLIPBOARD); gtk_clipboard_request_text (clipboard, - (GtkClipboardTextReceivedFunc) paste_text_received, - impl); + (GtkClipboardTextReceivedFunc) paste_text_received, + impl); } /* Implementation for GtkFileChooserEmbed::should_respond() */ @@ -10814,7 +15397,7 @@ check_preview_change (GtkFileChooserDefault *impl) if (impl->view_mode == VIEW_MODE_LIST) gtk_tree_view_get_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), &cursor_path, NULL); - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) cursor_path = NULL; else g_assert_not_reached (); @@ -10873,6 +15456,9 @@ check_preview_change (GtkFileChooserDefault *impl) g_free (new_display_name); } + + + update_preview_panel (impl); } static void @@ -11230,6 +15816,8 @@ list_selection_changed (void *selection, path_bar_update (impl); check_preview_change (impl); + + preview_panel_queue_update (impl); bookmarks_check_add_sensitivity (impl); g_signal_emit_by_name (impl, "selection-changed", 0); @@ -11521,6 +16109,7 @@ show_hidden_handler (GtkFileChooserDefault *impl) g_object_set (impl, "show-hidden", !impl->show_hidden, NULL); + } @@ -11665,7 +16254,7 @@ selection_selected_foreach (GtkFileChooserDefault *impl, selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_selected_foreach (selection, func, data); } - else if (view == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (view)) icon_view_selection_selected_foreach (impl, func, data); else g_assert_not_reached (); @@ -11688,7 +16277,7 @@ current_selection_count_selected_rows (GtkFileChooserDefault *impl) selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); return gtk_tree_selection_count_selected_rows (selection); } - if (impl->view_mode == VIEW_MODE_ICON) + if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { GList *icon_selection; icon_selection = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (impl->browse_files_icon_view)); @@ -11712,7 +16301,7 @@ selection_select_iter (GtkFileChooserDefault *impl, selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_select_iter (selection, iter); } - else if (target == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (target)) { GtkTreePath *path; path = gtk_tree_model_get_path (impl->current_model, iter); @@ -11768,7 +16357,7 @@ current_selection_unselect_iter (GtkFileChooserDefault *impl, selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_unselect_iter (selection, iter); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) { GtkTreePath *path; path = gtk_tree_model_get_path (impl->current_model, iter); @@ -11788,7 +16377,7 @@ current_selection_unselect_all (GtkFileChooserDefault *impl) selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_unselect_all (selection); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) gtk_icon_view_unselect_all (GTK_ICON_VIEW (impl->browse_files_icon_view)); else g_assert_not_reached (); @@ -11799,17 +16388,25 @@ current_view_set_file_model (GtkFileChooserDefault *impl, GtkTreeModel *model) { GtkWidget *view; + file_list_tooltip_cache_clear (impl); + file_icon_view_tooltip_cache_clear (impl); + + if (impl->current_model != model) + thumbnail_queue_clear (impl); + impl->current_model = model; if (impl->view_mode == VIEW_MODE_LIST) view = impl->browse_files_tree_view; - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) view = impl->browse_files_icon_view; else g_assert_not_reached (); g_object_set (view, "model", impl->current_model, NULL); + icon_view_visible_range_schedule (impl); + /* * If an icon-view scale change has queued a delayed full cache clear, a model * can become visible before that timeout fires. Clear the newly visible model @@ -11827,7 +16424,7 @@ current_view_set_cursor (GtkFileChooserDefault *impl, GtkTreePath *path) { if (impl->view_mode == VIEW_MODE_LIST) gtk_tree_view_set_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), path, NULL, FALSE); - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) gtk_icon_view_set_cursor (GTK_ICON_VIEW (impl->browse_files_icon_view), path, NULL, FALSE); else g_assert_not_reached (); @@ -11846,7 +16443,7 @@ current_view_set_select_multiple (GtkFileChooserDefault *impl, gboolean select_m gtk_tree_selection_set_mode (selection, mode); gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (impl->browse_files_tree_view), select_multiple); } - else if (impl->view_mode == VIEW_MODE_ICON) + else if (VIEW_MODE_USES_ICON_VIEW (impl->view_mode)) gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (impl->browse_files_icon_view), mode); else g_assert_not_reached (); diff --git a/gtk/gtkfilechooserprivate.h b/gtk/gtkfilechooserprivate.h index 193dea2e3..01e15dc40 100644 --- a/gtk/gtkfilechooserprivate.h +++ b/gtk/gtkfilechooserprivate.h @@ -122,8 +122,7 @@ struct _GtkFileChooserWidgetPrivate typedef enum { LOAD_EMPTY, /* There is no model */ - LOAD_PRELOAD, /* Model is loading and a timer is running; model isn't inserted into the tree yet */ - LOAD_LOADING, /* Timeout expired, model is inserted into the tree, but not fully loaded yet */ + LOAD_LOADING, /* Model is loading; inserted into the view when loading finishes */ LOAD_FINISHED /* Model is fully loaded and inserted into the tree */ } LoadState; @@ -139,7 +138,8 @@ typedef enum { typedef enum { VIEW_MODE_LIST, - VIEW_MODE_ICON + VIEW_MODE_ICON, + VIEW_MODE_COMPACT } ViewMode; typedef enum { @@ -176,13 +176,35 @@ struct _GtkFileChooserDefault GtkWidget *browse_shortcuts_popup_menu; GtkWidget *browse_shortcuts_popup_menu_remove_item; GtkWidget *browse_shortcuts_popup_menu_rename_item; + GtkWidget *browse_shortcuts_popup_menu_open_containing_folder_item; GtkWidget *browse_files_tree_view; GtkWidget *browse_files_scrolled_window; GtkWidget *browse_files_current_view; GtkWidget *browse_files_icon_view; + GtkTreePath *icon_tooltip_path; + gchar *icon_tooltip_text; + gint icon_tooltip_width; + ViewMode icon_tooltip_view_mode; + OperationMode icon_tooltip_operation_mode; + guint icon_tooltip_visible : 1; + GtkTreePath *list_tooltip_path; + GtkTreeModel *list_tooltip_model; + gchar *list_tooltip_text; + gint list_tooltip_width; + OperationMode list_tooltip_operation_mode; + guint list_tooltip_visible : 1; GtkWidget *browse_files_popup_menu; GtkWidget *browse_files_popup_menu_add_shortcut_item; + GtkWidget *browse_files_popup_menu_copy_item; + GtkWidget *browse_files_popup_menu_cut_item; + GtkWidget *browse_files_popup_menu_paste_item; + GtkWidget *browse_files_popup_menu_copy_location_item; + GtkWidget *browse_files_popup_menu_open_containing_folder_item; + GtkWidget *browse_files_popup_menu_rename_item; + GtkWidget *browse_files_popup_menu_trash_item; GtkWidget *browse_files_popup_menu_hidden_files_item; + GtkWidget *browse_files_popup_menu_preview_panel_item; + GtkWidget *browse_files_popup_menu_thumbnails_item; GtkWidget *browse_files_popup_menu_size_column_item; GtkWidget *browse_files_popup_menu_sort_by_name_item; GtkWidget *browse_files_popup_menu_sort_by_size_item; @@ -224,10 +246,18 @@ struct _GtkFileChooserDefault GtkWidget *preview_box; GtkWidget *preview_label; GtkWidget *preview_widget; + GtkWidget *preview_panel; + GtkWidget *preview_panel_image; + GtkWidget *preview_panel_title_label; + GtkWidget *preview_panel_info_label; + guint preview_panel_resize_timeout_id; + guint preview_panel_update_timeout_id; + gint preview_panel_last_image_size; GtkWidget *extra_align; GtkWidget *extra_widget; GtkWidget *view_mode_combo_box; + GtkWidget *preview_panel_toggle_button; GtkWidget *icon_view_scale_hbox; GtkWidget *icon_view_scale; GtkWidget *icon_view_scale_zoom_in_icon; @@ -237,8 +267,29 @@ struct _GtkFileChooserDefault guint icon_view_scale_after_layout_id; gint pending_icon_view_icon_size; gint icon_view_scale_visible_overscan; + + /* Cached viewport avoids an O(n) visible-range scan per icon cell. */ + guint icon_view_visible_range_idle_id; + gint icon_view_visible_first, icon_view_visible_last; + guint icon_view_visible_range_valid : 1; + ViewMode view_mode; + /* Visible-only thumbnail generation state. Thumbnail workers are + * cancellable so changing folders or views never leaves stale decode work + * running for files that are no longer useful to the user. + */ + GSList *thumbnail_queue; + GSList *thumbnail_queue_tail; + GHashTable *thumbnail_queued_files; + GHashTable *file_clipboard_cut_files; + GCancellable *thumbnail_cancellable; + guint thumbnail_queue_idle_id; + guint thumbnail_generation_serial; + guint thumbnail_worker_running : 1; + GdkPixbuf *thumbnail_standard_icon_pixbuf; + gint thumbnail_standard_icon_size; + GtkWidget *location_button; GtkWidget *location_entry_box; GtkWidget *location_label; @@ -264,7 +315,6 @@ struct _GtkFileChooserDefault LoadState load_state; ReloadState reload_state; - guint load_timeout_id; OperationMode operation_mode; @@ -294,6 +344,11 @@ struct _GtkFileChooserDefault GSource *edited_idle; char *edited_new_text; + GSource *rename_idle; + char *rename_new_text; + char *rename_old_text; + GFile *rename_file; + gulong settings_signal_id; int list_view_icon_size; int icon_view_icon_size; @@ -318,9 +373,11 @@ struct _GtkFileChooserDefault guint local_only : 1; guint preview_widget_active : 1; + guint preview_panel_active : 1; guint use_preview_label : 1; guint select_multiple : 1; guint show_hidden : 1; + guint show_thumbnails : 1; guint do_overwrite_confirmation : 1; guint list_sort_ascending : 1; guint changing_folder : 1; diff --git a/gtk/gtkfilechoosersettings.c b/gtk/gtkfilechoosersettings.c index ce34291ef..ceca18535 100644 --- a/gtk/gtkfilechoosersettings.c +++ b/gtk/gtkfilechoosersettings.c @@ -42,6 +42,8 @@ #define VIEW_MODE_KEY "ViewMode" #define ICON_VIEW_SCALE_KEY "IconViewScale" #define SHOW_HIDDEN_KEY "ShowHidden" +#define SHOW_PREVIEW_PANEL_KEY "ShowPreviewPanel" +#define SHOW_THUMBNAILS_KEY "ShowThumbnails" #define SHOW_SIZE_COLUMN_KEY "ShowSizeColumn" #define GEOMETRY_X_KEY "GeometryX" #define GEOMETRY_Y_KEY "GeometryY" @@ -65,6 +67,7 @@ #define MODE_LIST_VIEW "list-view" #define MODE_ICON_VIEW "icon-view" +#define MODE_COMPACT_VIEW "compact-view" #define EQ(a, b) (g_ascii_strcasecmp ((a), (b)) == 0) @@ -174,6 +177,8 @@ ensure_settings_read (GtkFileChooserSettings *settings) settings->view_mode = VIEW_MODE_LIST; else if (EQ (view_mode_str, MODE_ICON_VIEW)) settings->view_mode = VIEW_MODE_ICON; + else if (EQ (view_mode_str, MODE_COMPACT_VIEW)) + settings->view_mode = VIEW_MODE_COMPACT; else g_warning ("Unknown view mode '%s' encountered in filechooser settings", view_mode_str); @@ -194,6 +199,24 @@ ensure_settings_read (GtkFileChooserSettings *settings) else settings->show_hidden = value != FALSE; + /* Show preview panel */ + + value = g_key_file_get_boolean (key_file, SETTINGS_GROUP, + SHOW_PREVIEW_PANEL_KEY, &error); + if (error) + warn_if_invalid_key_and_clear_error (SHOW_PREVIEW_PANEL_KEY, &error); + else + settings->show_preview_panel = value != FALSE; + + /* Show thumbnails */ + + value = g_key_file_get_boolean (key_file, SETTINGS_GROUP, + SHOW_THUMBNAILS_KEY, &error); + if (error) + warn_if_invalid_key_and_clear_error (SHOW_THUMBNAILS_KEY, &error); + else + settings->show_thumbnails = value != FALSE; + /* Show size column */ value = g_key_file_get_boolean (key_file, SETTINGS_GROUP, @@ -287,6 +310,8 @@ _gtk_file_chooser_settings_init (GtkFileChooserSettings *settings) settings->sort_order = GTK_SORT_ASCENDING; settings->sort_column = FILE_LIST_COL_NAME; settings->show_hidden = FALSE; + settings->show_preview_panel = FALSE; + settings->show_thumbnails = TRUE; settings->show_size_column = TRUE; settings->geometry_x = -1; settings->geometry_y = -1; @@ -357,6 +382,34 @@ _gtk_file_chooser_settings_set_show_hidden (GtkFileChooserSettings *settings, settings->show_hidden = show_hidden != FALSE; } +gboolean +_gtk_file_chooser_settings_get_show_preview_panel (GtkFileChooserSettings *settings) +{ + ensure_settings_read (settings); + return settings->show_preview_panel; +} + +void +_gtk_file_chooser_settings_set_show_preview_panel (GtkFileChooserSettings *settings, + gboolean show_preview_panel) +{ + settings->show_preview_panel = show_preview_panel != FALSE; +} + +gboolean +_gtk_file_chooser_settings_get_show_thumbnails (GtkFileChooserSettings *settings) +{ + ensure_settings_read (settings); + return settings->show_thumbnails; +} + +void +_gtk_file_chooser_settings_set_show_thumbnails (GtkFileChooserSettings *settings, + gboolean show_thumbnails) +{ + settings->show_thumbnails = show_thumbnails != FALSE; +} + void _gtk_file_chooser_settings_set_show_size_column (GtkFileChooserSettings *settings, gboolean show_column) @@ -477,6 +530,8 @@ _gtk_file_chooser_settings_save (GtkFileChooserSettings *settings, view_mode_str = MODE_LIST_VIEW; else if (settings->view_mode == VIEW_MODE_ICON) view_mode_str = MODE_ICON_VIEW; + else if (settings->view_mode == VIEW_MODE_COMPACT) + view_mode_str = MODE_COMPACT_VIEW; else { g_assert_not_reached (); @@ -545,6 +600,10 @@ _gtk_file_chooser_settings_save (GtkFileChooserSettings *settings, ICON_VIEW_SCALE_KEY, settings->icon_view_scale); g_key_file_set_boolean (key_file, SETTINGS_GROUP, SHOW_HIDDEN_KEY, settings->show_hidden); + g_key_file_set_boolean (key_file, SETTINGS_GROUP, + SHOW_PREVIEW_PANEL_KEY, settings->show_preview_panel); + g_key_file_set_boolean (key_file, SETTINGS_GROUP, + SHOW_THUMBNAILS_KEY, settings->show_thumbnails); g_key_file_set_boolean (key_file, SETTINGS_GROUP, SHOW_SIZE_COLUMN_KEY, settings->show_size_column); g_key_file_set_integer (key_file, SETTINGS_GROUP, diff --git a/gtk/gtkfilechoosersettings.h b/gtk/gtkfilechoosersettings.h index b987fca8c..464cef15b 100644 --- a/gtk/gtkfilechoosersettings.h +++ b/gtk/gtkfilechoosersettings.h @@ -56,9 +56,11 @@ struct _GtkFileChooserSettings int geometry_width; int geometry_height; - guint settings_read : 1; - guint show_hidden : 1; - guint show_size_column : 1; + guint settings_read : 1; + guint show_hidden : 1; + guint show_preview_panel : 1; + guint show_thumbnails : 1; + guint show_size_column : 1; }; struct _GtkFileChooserSettingsClass @@ -86,6 +88,14 @@ gboolean _gtk_file_chooser_settings_get_show_hidden (GtkFileChooserSettings *set void _gtk_file_chooser_settings_set_show_hidden (GtkFileChooserSettings *settings, gboolean show_hidden); +gboolean _gtk_file_chooser_settings_get_show_preview_panel (GtkFileChooserSettings *settings); +void _gtk_file_chooser_settings_set_show_preview_panel (GtkFileChooserSettings *settings, + gboolean show_preview_panel); + +gboolean _gtk_file_chooser_settings_get_show_thumbnails (GtkFileChooserSettings *settings); +void _gtk_file_chooser_settings_set_show_thumbnails (GtkFileChooserSettings *settings, + gboolean show_thumbnails); + gboolean _gtk_file_chooser_settings_get_show_size_column (GtkFileChooserSettings *settings); void _gtk_file_chooser_settings_set_show_size_column (GtkFileChooserSettings *settings, gboolean show_column); diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index 52fd17a08..5e2c6b7c8 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -36,89 +36,17 @@ /*** Structure: how GtkFileSystemModel works * - * This is a custom GtkTreeModel used to hold a collection of files for GtkFileChooser. There are two use cases: + * File nodes have stable IDs in fixed-size slot storage. model->order holds + * logical sort order and model->visible_order is the GtkTreeModel projection. + * file_lookup permanently maps GFile to stable node ID. * - * 1. The model populates itself from a folder, using the GIO file enumerator API. This happens if you use - * _gtk_file_system_model_new_for_directory(). This is the normal usage for showing the contents of a folder. + * Cached column values live in sparse 64-ID pages allocated per used column, + * so unused columns have no per-node GValue storage. * - * 2. The caller populates the model by hand, with files not necessarily in the same folder. This happens - * if you use _gtk_file_system_model_new() and then _gtk_file_system_model_add_and_query_file(). This is - * the special kind of usage for "search" and "recent-files", where the file chooser gives the model the - * files to be displayed. - * - * Internal data structure - * ----------------------- - * - * Each file is kept in a FileModelNode structure. Each FileModelNode holds a GFile* and other data. All the - * node structures have the same size, determined at runtime, depending on the number of columns that were passed - * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is - * not sizeof (FileModelNode), but rather model->node_size). The last field in the FileModelNode structure, - * node->values[], is an array of GValue, used to hold the data for those columns. - * - * The model stores an array of FileModelNode structures in model->files. This is a GArray where each element is - * model->node_size bytes in size (the model computes that node size when initializing itself). There are - * convenience macros, get_node() and node_index(), to access that array based on an array index or a pointer to - * a node inside the array. - * - * The model accesses files through two of its fields: - * - * model->files - GArray of FileModelNode structures. - * - * model->file_lookup - hash table that maps a GFile* to an index inside the model->files array. - * - * The model->file_lookup hash table is populated lazily. It is both accessed and populated with the - * node_get_for_file() function. The invariant is that the files in model->files[n] for n < g_hash_table_size - * (model->file_lookup) are already added to the hash table. The hash table will get cleared when we re-sort the - * files, as the array will be in a different order and the indexes need to be rebuilt. - * - * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView. - * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing - * hidden files. Also, a file filter may be explicitly set onto the model, for example, to only show files that - * match "*.jpg". In this case, node->filtered_out says whether the node failed the filter. The ultimate - * decision on whether a node is visible or not in the treeview is distilled into the node->visible field. - * The reason for having a separate node->filtered_out field is so that the file chooser can query whether - * a (filtered-out) folder should be made sensitive in the GUI. - * - * Visible rows vs. possibly-invisible nodes - * ----------------------------------------- - * - * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from - * the treeview to array indexes in our array of files. And thus we introduce a bit of terminology: - * - * index - An index in the model->files array. All variables/fields that represent indexes are either called - * "index" or "i_*", or simply "i" for things like loop counters. - * - * row - An index in the GtkTreeView, i.e. the index of a row within the outward-facing API of the - * GtkFileSystemModel. However, note that our rows are 1-based, not 0-based, for the reason explained in the - * following paragraph. Variables/fields that represent visible rows are called "row", or "r_*", or simply - * "r". - * - * Each FileModelNode has a node->row field which is the number of visible rows in the treeview, *before and - * including* that node. This means that node->row is 1-based, instead of 0-based --- this makes some code - * simpler, believe it or not :) This also means that when the calling GtkTreeView gives us a GtkTreePath, we - * turn the 0-based treepath into a 1-based row for our purposes. If a node is not visible, it will have the - * same row number as its closest preceding visible node. - * - * We try to compute the node->row fields lazily. A node is said to be "valid" if its node->row is accurate. - * For this, the model keeps a model->n_nodes_valid field which is the count of valid nodes starting from the - * beginning of the model->files array. When a node changes its information, or when a node gets deleted, that - * node and the following ones get invalidated by simply setting model->n_nodes_valid to the array index of the - * node. If the model happens to need a node's row number and that node is in the model->files array after - * model->n_nodes_valid, then the nodes get re-validated up to the sought node. See node_validate_rows() for - * this logic. - * - * You never access a node->row directly. Instead, call node_get_tree_row(). That function will validate the nodes - * up to the sought one if the node is not valid yet, and it will return a proper 0-based row. - * - * Sorting - * ------- - * - * The model implements the GtkTreeSortable interface. To avoid re-sorting - * every time a node gets added (which would lead to O(n^2) performance during - * the initial population of the model), the model can freeze itself (with - * freeze_updates()) during the intial population process. When the model is - * frozen, sorting will not happen. The model will sort itself when the freeze - * count goes back to zero, via corresponding calls to thaw_updates(). + * Directory enumeration uses one bounded GIO batch as its pending queue. + * Integration is cooperatively drained in short main-loop slices; the next + * GIO batch is requested only after the current one has been consumed. + * Editing has its own hold state and no longer reuses directory batching. */ /*** DEFINES ***/ @@ -130,69 +58,107 @@ /* random number that everyone else seems to use, too */ #define FILES_PER_QUERY 100 +#define MODEL_DRAIN_SLICE_US 2000 +#define MODEL_DRAIN_CLOCK_GRANULARITY 4 +#define MODEL_FINALIZE_CLOCK_GRANULARITY 8 + +enum +{ + MODEL_FINALIZE_NONE, + MODEL_FINALIZE_MERGE, + MODEL_FINALIZE_VISIBLE, + MODEL_FINALIZE_REPLAY +}; + +#define VALUE_PAGE_SHIFT 6 +#define VALUE_PAGE_SIZE (1u << VALUE_PAGE_SHIFT) +#define VALUE_PAGE_MASK (VALUE_PAGE_SIZE - 1u) typedef struct _FileModelNode FileModelNode; typedef struct _GtkFileSystemModelClass GtkFileSystemModelClass; struct _FileModelNode { - GFile * file; /* file represented by this node or NULL for editable */ - GFileInfo * info; /* info for this file or NULL if unknown */ + GFile * file; + GFileInfo * info; - guint row; /* if valid (see model->n_valid_indexes), visible nodes before and including - * this one - see the "Structure" comment above. - */ + guint order_pos; + guint visible_pos; + guint generation; - guint visible :1; /* if the file is currently visible */ - guint filtered_out :1;/* if the file is currently filtered out (i.e. it didn't pass the filters) */ - guint frozen_add :1; /* true if the model was frozen and the entry has not been added yet */ - - GValue values[1]; /* actually n_columns values */ + guint visible :1; + guint filtered_out :1; + guint held_add :1; }; +typedef struct +{ + GValue values[VALUE_PAGE_SIZE]; +} ValuePage; + +typedef struct +{ + GFile *file; + GFileMonitorEvent type; +} PendingMonitorChange; + +typedef GWeakRef PendingMonitorQuery; + struct _GtkFileSystemModel { GObject parent_instance; + GFile * dir; + char * attributes; + GFileMonitor * dir_monitor; + GFileEnumerator * dir_enumerator; + GCancellable * cancellable; + GList * pending_files; + guint drain_source; - GFile * dir; /* directory that's displayed */ - guint dir_thaw_source;/* GSource id for unfreezing the model */ - char * attributes; /* attributes the file info must contain, or NULL for all attributes */ - GFileMonitor * dir_monitor; /* directory that is monitored, or NULL if monitoring was not supported */ + GArray * sort_scratch; + guint sort_width; + guint sort_left; + guint sort_i; + guint sort_j; + guint sort_k; + guint sort_mid; + guint sort_right; + guint finalize_phase; + GError * load_error; - GCancellable * cancellable; /* cancellable in use for all operations - cancelled on dispose */ - GArray * files; /* array of FileModelNode containing all our files */ - gsize node_size; /* Size of a FileModelNode structure once its ->values field has n_columns */ - guint n_nodes_valid; /* count of valid nodes (i.e. those whose node->row is accurate) */ - GHashTable * file_lookup; /* mapping of GFile => array index in model->files - * This hash table doesn't always have the same number of entries as the files array; - * it can get cleared completely when we resort. - * The hash table gets re-populated in node_get_for_file() if this mismatch is - * detected. - */ + GQueue * pending_monitor_changes; + GHashTable * pending_monitor_queries; - guint n_columns; /* number of columns */ - GType * column_types; /* types of each column */ - GtkFileSystemModelGetValue get_func; /* function to call to fill in values in columns */ - gpointer get_data; /* data to pass to get_func */ + GArray * files; + GArray * order; + GArray * visible_order; + guint free_id_head; + GHashTable * file_lookup; - GtkFileFilter * filter; /* filter to use for deciding which nodes are visible */ + guint n_columns; + GType * column_types; + GPtrArray ** value_pages; + GtkFileSystemModelGetValue get_func; + gpointer get_data; - int sort_column_id; /* current sorting column */ - GtkSortType sort_order; /* current sorting order */ - GList * sort_list; /* list of sorting functions */ - GtkTreeIterCompareFunc default_sort_func; /* default sort function */ - gpointer default_sort_data; /* data to pass to default sort func */ - GDestroyNotify default_sort_destroy; /* function to call to destroy default_sort_data */ + GtkFileFilter * filter; - guint frozen; /* number of times we're frozen */ + int sort_column_id; + GtkSortType sort_order; + GList * sort_list; + GtkTreeIterCompareFunc default_sort_func; + gpointer default_sort_data; + GDestroyNotify default_sort_destroy; - gboolean filter_on_thaw :1;/* set when filtering needs to happen upon thawing */ - gboolean sort_on_thaw :1;/* set when sorting needs to happen upon thawing */ + guint editing_hold :1; + guint refilter_pending :1; + guint resort_pending :1; + guint directory_loading :1; - guint show_hidden :1; /* whether to show hidden files */ - guint show_folders :1;/* whether to show folders */ - guint show_files :1; /* whether to show files */ - guint filter_folders :1;/* whether filter applies to folders */ + guint show_hidden :1; + guint show_folders :1; + guint show_files :1; + guint filter_folders :1; }; #define GTK_FILE_SYSTEM_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass)) @@ -208,8 +174,17 @@ struct _GtkFileSystemModelClass void (*finished_loading) (GtkFileSystemModel *model, GError *error); }; -static void freeze_updates (GtkFileSystemModel *model); -static void thaw_updates (GtkFileSystemModel *model); +static void gtk_file_system_model_got_files (GObject *object, + GAsyncResult *res, + gpointer data); +static gboolean gtk_file_system_model_drain (gpointer data); +static void gtk_file_system_model_schedule_work (GtkFileSystemModel *model); +static gboolean gtk_file_system_model_finalize_load (gpointer data); +static void gtk_file_system_model_begin_finalize_load (GtkFileSystemModel *model, + GError *error); +static void gtk_file_system_model_apply_monitor_change (GtkFileSystemModel *model, + GFile *file, + GFileMonitorEvent type); static guint node_get_for_file (GtkFileSystemModel *model, GFile *file); @@ -221,89 +196,118 @@ static void remove_file (GtkFileSystemModel *model, GFile *file); /* iter setup: - * @user_data: the model - * @user_data2: GUINT_TO_POINTER of array index of current entry - * - * All other fields are unused. Note that the array index does not corrspond - * 1:1 with the path index as entries might not be visible. + * @user_data: the model + * @user_data2: stable node ID + * @user_data3: node generation */ -#define ITER_INDEX(iter) GPOINTER_TO_UINT((iter)->user_data2) -#define ITER_IS_VALID(model, iter) ((model) == (iter)->user_data) -#define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START {\ - g_assert (_index < (model)->files->len); \ +#define ITER_INDEX(iter) GPOINTER_TO_UINT ((iter)->user_data2) + +#define get_node(_model, _index) \ + (&g_array_index ((_model)->files, FileModelNode, (_index))) + +static gboolean +iter_is_valid (GtkFileSystemModel *model, + GtkTreeIter *iter) +{ + guint id; + FileModelNode *node; + + if (iter == NULL || iter->user_data != model) + return FALSE; + + id = ITER_INDEX (iter); + if (id >= model->files->len) + return FALSE; + + node = get_node (model, id); + return (id == 0 || node->file != NULL) && + node->generation == GPOINTER_TO_UINT (iter->user_data3); +} + +#define ITER_IS_VALID(model, iter) iter_is_valid ((model), (iter)) + +#define ITER_INIT_FROM_INDEX(model, _iter, _index) G_STMT_START { \ + FileModelNode *_node; \ + g_assert ((_index) < (model)->files->len); \ + _node = get_node ((model), (_index)); \ + g_assert ((_index) == 0 || _node->file != NULL); \ (_iter)->user_data = (model); \ (_iter)->user_data2 = GUINT_TO_POINTER (_index); \ -}G_STMT_END + (_iter)->user_data3 = GUINT_TO_POINTER (_node->generation); \ +} G_STMT_END -/*** FileModelNode ***/ - -/* Get a FileModelNode structure given an index in the model->files array of nodes */ -#define get_node(_model, _index) ((FileModelNode *) ((_model)->files->data + (_index) * (_model)->node_size)) - -/* Get an index within the model->files array of nodes, given a FileModelNode* */ -#define node_index(_model, _node) (((gchar *) (_node) - (_model)->files->data) / (_model)->node_size) - -/* @up_to_index: smallest model->files array index that will be valid after this call - * @up_to_row: smallest node->row that will be valid after this call - * - * If you want to validate up to an index or up to a row, specify the index or - * the row you want and specify G_MAXUINT for the other argument. Pass - * G_MAXUINT for both arguments for "validate everything". - */ static void -node_validate_rows (GtkFileSystemModel *model, guint up_to_index, guint up_to_row) +order_update_positions_from (GtkFileSystemModel *model, + guint first) { - guint i, row; + guint i; - if (model->files->len == 0) - return; - - up_to_index = MIN (up_to_index, model->files->len - 1); - - i = model->n_nodes_valid; - if (i != 0) - row = get_node (model, i - 1)->row; - else - row = 0; - - while (i <= up_to_index && row <= up_to_row) + for (i = first; i < model->order->len; i++) { - FileModelNode *node = get_node (model, i); - if (node->visible) - row++; - node->row = row; - i++; + guint id = g_array_index (model->order, guint, i); + get_node (model, id)->order_pos = i; + } +} + +static void +visible_update_positions_from (GtkFileSystemModel *model, + guint first) +{ + guint i; + + for (i = first; i < model->visible_order->len; i++) + { + guint id = g_array_index (model->visible_order, guint, i); + get_node (model, id)->visible_pos = i; } - model->n_nodes_valid = i; } static guint -node_get_tree_row (GtkFileSystemModel *model, guint index) +visible_find_order_pos (GtkFileSystemModel *model, + guint order_pos, + gboolean after) { - if (model->n_nodes_valid <= index) - node_validate_rows (model, index, G_MAXUINT); + guint lo = get_node (model, 0)->visible ? 1 : 0; + guint hi = model->visible_order->len; - return get_node (model, index)->row - 1; -} + while (lo < hi) + { + guint mid = lo + (hi - lo) / 2; + guint id = g_array_index (model->visible_order, guint, mid); + guint pos = get_node (model, id)->order_pos; -static void -node_invalidate_index (GtkFileSystemModel *model, guint id) -{ - model->n_nodes_valid = MIN (model->n_nodes_valid, id); + if (pos < order_pos || (after && pos == order_pos)) + lo = mid + 1; + else + hi = mid; + } + + return lo; } static GtkTreePath * -tree_path_new_from_node (GtkFileSystemModel *model, guint id) +tree_path_new_from_node (GtkFileSystemModel *model, + guint id) { - guint r = node_get_tree_row (model, id); + FileModelNode *node = get_node (model, id); + guint row = G_MAXUINT; - g_assert (r < model->files->len); + if (node->visible) + row = node->visible_pos; + else if (id != 0) + { + guint pos = visible_find_order_pos (model, node->order_pos, TRUE); + if (pos > 0) + row = pos - 1; + } - return gtk_tree_path_new_from_indices (r, -1); + g_assert (row != G_MAXUINT); + return gtk_tree_path_new_from_indices (row, -1); } static void -emit_row_inserted_for_node (GtkFileSystemModel *model, guint id) +emit_row_inserted_for_node (GtkFileSystemModel *model, + guint id) { GtkTreePath *path; GtkTreeIter iter; @@ -315,7 +319,8 @@ emit_row_inserted_for_node (GtkFileSystemModel *model, guint id) } static void -emit_row_changed_for_node (GtkFileSystemModel *model, guint id) +emit_row_changed_for_node (GtkFileSystemModel *model, + guint id) { GtkTreePath *path; GtkTreeIter iter; @@ -327,7 +332,8 @@ emit_row_changed_for_node (GtkFileSystemModel *model, guint id) } static void -emit_row_deleted_for_row (GtkFileSystemModel *model, guint row) +emit_row_deleted_for_row (GtkFileSystemModel *model, + guint row) { GtkTreePath *path; @@ -337,11 +343,167 @@ emit_row_deleted_for_row (GtkFileSystemModel *model, guint row) } static void -node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out) +visible_remove_node (GtkFileSystemModel *model, + guint id) { FileModelNode *node = get_node (model, id); + guint pos; - /* Filteredness */ + g_assert (node->visible); + pos = node->visible_pos; + + g_array_remove_index (model->visible_order, pos); + node->visible = FALSE; + visible_update_positions_from (model, pos); +} + +static void +value_page_free (gpointer data) +{ + ValuePage *page = data; + guint slot; + + if (page) + for (slot = 0; slot < VALUE_PAGE_SIZE; slot++) + if (G_VALUE_TYPE (&page->values[slot]) != G_TYPE_INVALID) + g_value_unset (&page->values[slot]); + + g_free (page); +} + +static void +file_model_node_clear (gpointer data) +{ + FileModelNode *node = data; + + g_clear_object (&node->file); + g_clear_object (&node->info); +} + +static GValue * +value_cache_slot (GtkFileSystemModel *model, + guint id, + guint column, + gboolean create) +{ + GPtrArray *pages; + ValuePage *page; + guint page_index; + + g_assert (column < model->n_columns); + + pages = model->value_pages[column]; + if (pages == NULL) + { + if (!create) + return NULL; + + pages = g_ptr_array_new_with_free_func (value_page_free); + model->value_pages[column] = pages; + } + + page_index = id >> VALUE_PAGE_SHIFT; + if (page_index >= pages->len) + { + if (!create) + return NULL; + + g_ptr_array_set_size (pages, page_index + 1); + } + + page = pages->pdata[page_index]; + if (page == NULL && create) + { + page = g_new0 (ValuePage, 1); + pages->pdata[page_index] = page; + } + + if (page == NULL) + return NULL; + + return &page->values[id & VALUE_PAGE_MASK]; +} + +static gboolean +value_cache_clear_slot (GtkFileSystemModel *model, + guint id, + guint column) +{ + GValue *value = value_cache_slot (model, id, column, FALSE); + + if (value == NULL || G_VALUE_TYPE (value) == G_TYPE_INVALID) + return FALSE; + + g_value_unset (value); + return TRUE; +} + +static void +value_cache_clear_node (GtkFileSystemModel *model, + guint id) +{ + guint column; + + for (column = 0; column < model->n_columns; column++) + value_cache_clear_slot (model, id, column); +} + + + +static guint +node_allocate_slot (GtkFileSystemModel *model) +{ + FileModelNode *node; + guint id; + + if (model->free_id_head != 0) + { + id = model->free_id_head; + node = get_node (model, id); + model->free_id_head = node->order_pos; + } + else + { + id = model->files->len; + g_array_set_size (model->files, id + 1); + node = get_node (model, id); + node->generation = 1; + } + + return id; +} + +static void +node_retire_slot (GtkFileSystemModel *model, + guint id) +{ + FileModelNode *node; + guint generation; + + g_assert (id != 0); + + node = get_node (model, id); + file_model_node_clear (node); + value_cache_clear_node (model, id); + + generation = node->generation + 1; + if (generation == 0) + generation = 1; + + memset (node, 0, sizeof (*node)); + node->generation = generation; + node->order_pos = model->free_id_head; + + model->free_id_head = id; +} + +static void +node_set_visible_and_filtered_out (GtkFileSystemModel *model, + guint id, + gboolean visible, + gboolean filtered_out) +{ + FileModelNode *node = get_node (model, id); if (node->filtered_out != filtered_out) { @@ -350,27 +512,25 @@ node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean emit_row_changed_for_node (model, id); } - /* Visibility */ - - if (node->visible == visible || - node->frozen_add) + if (node->visible == visible || node->held_add) return; if (visible) { + guint pos; + + g_assert (!node->visible); + pos = id == 0 ? 0 : + visible_find_order_pos (model, node->order_pos, FALSE); + g_array_insert_val (model->visible_order, pos, id); node->visible = TRUE; - node_invalidate_index (model, id); + visible_update_positions_from (model, pos); emit_row_inserted_for_node (model, id); } else { - guint row; - - row = node_get_tree_row (model, id); - g_assert (row < model->files->len); - - node->visible = FALSE; - node_invalidate_index (model, id); + guint row = node->visible_pos; + visible_remove_node (model, id); emit_row_deleted_for_row (model, row); } } @@ -524,73 +684,29 @@ gtk_file_system_model_get_column_type (GtkTreeModel *tree_model, return model->column_types[i]; } -static int -compare_indices (gconstpointer key, gconstpointer _node) -{ - const FileModelNode *node = _node; - - return GPOINTER_TO_UINT (key) - node->row; -} - static gboolean gtk_file_system_model_iter_nth_child (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreeIter *parent, - gint n) + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); - char *node; guint id; - guint row_to_find; g_return_val_if_fail (n >= 0, FALSE); - if (parent != NULL) + if (parent != NULL || (guint) n >= model->visible_order->len) return FALSE; - row_to_find = n + 1; /* plus one as our node->row numbers are 1-based; see the "Structure" comment at the beginning */ - - if (model->n_nodes_valid > 0 && - get_node (model, model->n_nodes_valid - 1)->row >= row_to_find) - { - /* Fast path - the nodes are valid up to the sought one. - * - * First, find a node with the sought row number...*/ - - node = bsearch (GUINT_TO_POINTER (row_to_find), - model->files->data, - model->n_nodes_valid, - model->node_size, - compare_indices); - if (node == NULL) - return FALSE; - - /* ... Second, back up until we find the first visible node with that row number */ - - id = node_index (model, node); - while (!get_node (model, id)->visible) - id--; - - g_assert (get_node (model, id)->row == row_to_find); - } - else - { - /* Slow path - the nodes need to be validated up to the sought one */ - - node_validate_rows (model, G_MAXUINT, n); /* note that this is really "n", not row_to_find - see node_validate_rows() */ - id = model->n_nodes_valid - 1; - if (model->n_nodes_valid == 0 || get_node (model, id)->row != row_to_find) - return FALSE; - } - + id = g_array_index (model->visible_order, guint, n); ITER_INIT_FROM_INDEX (model, iter, id); return TRUE; } static gboolean gtk_file_system_model_get_iter (GtkTreeModel *tree_model, - GtkTreeIter *iter, - GtkTreePath *path) + GtkTreeIter *iter, + GtkTreePath *path) { g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); @@ -605,12 +721,11 @@ gtk_file_system_model_get_iter (GtkTreeModel *tree_model, static GtkTreePath * gtk_file_system_model_get_path (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); - return tree_path_new_from_node (model, ITER_INDEX (iter)); } @@ -638,25 +753,29 @@ gtk_file_system_model_get_value (GtkTreeModel *tree_model, static gboolean gtk_file_system_model_iter_next (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); - guint i; + FileModelNode *node; + guint pos; g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); - for (i = ITER_INDEX (iter) + 1; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); + node = get_node (model, ITER_INDEX (iter)); - if (node->visible) - { - ITER_INIT_FROM_INDEX (model, iter, i); - return TRUE; - } - } + if (node->visible) + pos = node->visible_pos + 1; + else if (ITER_INDEX (iter) != 0) + pos = visible_find_order_pos (model, node->order_pos, TRUE); + else + return FALSE; - return FALSE; + if (pos >= model->visible_order->len) + return FALSE; + + ITER_INIT_FROM_INDEX (model, iter, + g_array_index (model->visible_order, guint, pos)); + return TRUE; } static gboolean @@ -676,14 +795,14 @@ gtk_file_system_model_iter_has_child (GtkTreeModel *tree_model, static gint gtk_file_system_model_iter_n_children (GtkTreeModel *tree_model, - GtkTreeIter *iter) + GtkTreeIter *iter) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (tree_model); if (iter) return 0; - return node_get_tree_row (model, model->files->len - 1) + 1; + return model->visible_order->len; } static gboolean @@ -743,7 +862,7 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) { GtkTreeDataSortHeader *header; - if (model->files->len <= 2) + if (model->order->len <= 1) return FALSE; switch (model->sort_column_id) @@ -757,7 +876,8 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) case GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID: return FALSE; default: - header = _gtk_tree_data_list_get_header (model->sort_list, model->sort_column_id); + header = _gtk_tree_data_list_get_header (model->sort_list, + model->sort_column_id); if (header == NULL) return FALSE; data->func = header->func; @@ -771,14 +891,67 @@ sort_data_init (SortData *data, GtkFileSystemModel *model) } static int -compare_array_element (gconstpointer a, gconstpointer b, gpointer user_data) +compare_order_element (gconstpointer a, gconstpointer b, gpointer user_data) { SortData *data = user_data; GtkTreeIter itera, iterb; + guint ida = *(const guint *) a; + guint idb = *(const guint *) b; - ITER_INIT_FROM_INDEX (data->model, &itera, node_index (data->model, a)); - ITER_INIT_FROM_INDEX (data->model, &iterb, node_index (data->model, b)); - return data->func (GTK_TREE_MODEL (data->model), &itera, &iterb, data->data) * data->order; + ITER_INIT_FROM_INDEX (data->model, &itera, ida); + ITER_INIT_FROM_INDEX (data->model, &iterb, idb); + + return data->func (GTK_TREE_MODEL (data->model), + &itera, &iterb, data->data) * data->order; +} + +static void +visible_order_rebuild_after_sort (GtkFileSystemModel *model) +{ + guint old_len = model->visible_order->len; + int *new_order; + guint row = 0; + guint i; + FileModelNode *editable; + + if (old_len == 0) + return; + + new_order = g_new (int, old_len); + g_array_set_size (model->visible_order, 0); + + editable = get_node (model, 0); + if (editable->visible) + { + guint id = 0; + new_order[row] = editable->visible_pos; + g_array_append_val (model->visible_order, id); + editable->visible_pos = row++; + } + + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + FileModelNode *node = get_node (model, id); + + if (!node->visible) + continue; + + new_order[row] = node->visible_pos; + g_array_append_val (model->visible_order, id); + node->visible_pos = row++; + } + + g_assert (row == old_len); + + { + GtkTreePath *path = gtk_tree_path_new (); + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, NULL, new_order); + gtk_tree_path_free (path); + } + + g_free (new_order); } static void @@ -786,67 +959,26 @@ gtk_file_system_model_sort (GtkFileSystemModel *model) { SortData data; - if (model->frozen) + if (model->directory_loading || model->editing_hold) { - model->sort_on_thaw = TRUE; + model->resort_pending = TRUE; return; } if (sort_data_init (&data, model)) { - GtkTreePath *path; - guint i; - guint r, n_visible_rows; - - node_validate_rows (model, G_MAXUINT, G_MAXUINT); - n_visible_rows = node_get_tree_row (model, model->files->len - 1) + 1; - model->n_nodes_valid = 0; - g_hash_table_remove_all (model->file_lookup); - _gtk_sort_array (get_node (model, 1), /* start at index 1; don't sort the editable row */ - model->files->len - 1, - model->node_size, - compare_array_element, + _gtk_sort_array (model->order->data, + model->order->len, + sizeof (guint), + compare_order_element, &data); - g_assert (model->n_nodes_valid == 0); - g_assert (g_hash_table_size (model->file_lookup) == 0); - if (n_visible_rows) - { - int *new_order = g_new (int, n_visible_rows); - - r = 0; - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - if (!node->visible) - { - node->row = r; - continue; - } - - new_order[r] = node->row - 1; - r++; - node->row = r; - } - g_assert (r == n_visible_rows); - path = gtk_tree_path_new (); - gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), - path, - NULL, - new_order); - gtk_tree_path_free (path); - g_free (new_order); - } + order_update_positions_from (model, 0); + visible_order_rebuild_after_sort (model); } - model->sort_on_thaw = FALSE; + model->resort_pending = FALSE; } -static void -gtk_file_system_model_sort_node (GtkFileSystemModel *model, guint node) -{ - /* FIXME: improve */ - gtk_file_system_model_sort (model); -} static gboolean gtk_file_system_model_get_sort_column_id (GtkTreeSortable *sortable, @@ -1023,6 +1155,280 @@ enum { static guint file_system_model_signals[LAST_SIGNAL] = { 0 }; +static void +pending_monitor_change_free (gpointer data) +{ + PendingMonitorChange *change = data; + + g_object_unref (change->file); + g_free (change); +} + +static void +gtk_file_system_model_prepare_merge (GtkFileSystemModel *model) +{ + guint n = model->order->len; + + model->sort_mid = MIN (model->sort_left + model->sort_width, n); + model->sort_right = MIN (model->sort_left + 2 * model->sort_width, n); + model->sort_i = model->sort_left; + model->sort_j = model->sort_mid; + model->sort_k = model->sort_left; +} + +static void +gtk_file_system_model_prepare_visible (GtkFileSystemModel *model) +{ + FileModelNode *editable = get_node (model, 0); + + g_array_set_size (model->visible_order, 0); + if (editable->visible) + { + guint id = 0; + + editable->visible_pos = 0; + g_array_append_val (model->visible_order, id); + } + + model->sort_left = 0; + model->finalize_phase = MODEL_FINALIZE_VISIBLE; + model->refilter_pending = FALSE; +} + +static void +gtk_file_system_model_restart_initial_sort (GtkFileSystemModel *model) +{ + SortData data; + guint n = model->order->len; + + model->resort_pending = FALSE; + + if (sort_data_init (&data, model)) + { + if (model->sort_scratch == NULL) + model->sort_scratch = + g_array_sized_new (FALSE, FALSE, sizeof (guint), n); + g_array_set_size (model->sort_scratch, n); + + model->sort_width = 1; + model->sort_left = 0; + gtk_file_system_model_prepare_merge (model); + model->finalize_phase = MODEL_FINALIZE_MERGE; + } + else + gtk_file_system_model_prepare_visible (model); +} + +static void +gtk_file_system_model_schedule_work (GtkFileSystemModel *model) +{ + GSourceFunc func; + + if (model->drain_source != 0 || model->editing_hold) + return; + + if (model->pending_files != NULL) + func = gtk_file_system_model_drain; + else if (model->finalize_phase != MODEL_FINALIZE_NONE) + func = gtk_file_system_model_finalize_load; + else + return; + + model->drain_source = + gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE, + func, + g_object_ref (model), + g_object_unref); +} + +static void +gtk_file_system_model_begin_finalize_load (GtkFileSystemModel *model, + GError *error) +{ + g_assert (model->directory_loading); + g_assert (model->finalize_phase == MODEL_FINALIZE_NONE); + g_assert (model->pending_files == NULL); + + g_clear_error (&model->load_error); + model->load_error = error ? g_error_copy (error) : NULL; + + gtk_file_system_model_restart_initial_sort (model); + gtk_file_system_model_schedule_work (model); +} + +static gboolean +gtk_file_system_model_finalize_load (gpointer data) +{ + GtkFileSystemModel *model = data; + gint64 deadline = g_get_monotonic_time () + MODEL_DRAIN_SLICE_US; + guint processed = 0; + + if (model->editing_hold && model->directory_loading) + { + model->drain_source = 0; + return FALSE; + } + + if (model->resort_pending) + gtk_file_system_model_restart_initial_sort (model); + + while (TRUE) + { + guint n = model->order->len; + + if (model->finalize_phase == MODEL_FINALIZE_MERGE) + { + SortData data; + + if (!sort_data_init (&data, model)) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + while (model->sort_k < model->sort_right) + { + guint id; + + if (model->sort_i < model->sort_mid && + (model->sort_j >= model->sort_right || + compare_order_element + (&g_array_index (model->order, guint, model->sort_i), + &g_array_index (model->order, guint, model->sort_j), + &data) <= 0)) + id = g_array_index (model->order, guint, model->sort_i++); + else + id = g_array_index (model->order, guint, model->sort_j++); + + g_array_index (model->sort_scratch, guint, model->sort_k++) = id; + + if ((++processed % MODEL_FINALIZE_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + model->sort_left = model->sort_right; + if (model->sort_left < n) + { + gtk_file_system_model_prepare_merge (model); + continue; + } + + /* Make the completed pass canonical and reuse the old buffer. */ + { + GArray *tmp = model->order; + + model->order = model->sort_scratch; + model->sort_scratch = tmp; + } + + if (model->resort_pending) + { + gtk_file_system_model_restart_initial_sort (model); + continue; + } + + if (model->sort_width >= n / 2 + n % 2) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + model->sort_width *= 2; + model->sort_left = 0; + gtk_file_system_model_prepare_merge (model); + continue; + } + + if (model->finalize_phase == MODEL_FINALIZE_VISIBLE) + { + if (model->refilter_pending) + gtk_file_system_model_prepare_visible (model); + + while (model->sort_left < n) + { + guint pos = model->sort_left++; + guint id = g_array_index (model->order, guint, pos); + FileModelNode *node = get_node (model, id); + gboolean filtered_out; + gboolean visible; + + node->order_pos = pos; + filtered_out = node_should_be_filtered_out (model, id); + visible = node_should_be_visible (model, id, filtered_out); + + node->filtered_out = filtered_out; + node->visible = visible; + + if (visible) + { + node->visible_pos = model->visible_order->len; + g_array_append_val (model->visible_order, id); + } + + if ((++processed % MODEL_FINALIZE_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + if (model->resort_pending) + { + gtk_file_system_model_restart_initial_sort (model); + continue; + } + + if (model->refilter_pending) + { + gtk_file_system_model_prepare_visible (model); + continue; + } + + model->directory_loading = FALSE; + model->refilter_pending = FALSE; + model->resort_pending = FALSE; + g_clear_pointer (&model->sort_scratch, g_array_unref); + + model->finalize_phase = MODEL_FINALIZE_REPLAY; + if (g_get_monotonic_time () >= deadline) + return TRUE; + continue; + } + + if (model->finalize_phase == MODEL_FINALIZE_REPLAY) + { + while (model->pending_monitor_changes != NULL && + !g_queue_is_empty (model->pending_monitor_changes)) + { + PendingMonitorChange *change; + + change = g_queue_pop_head (model->pending_monitor_changes); + + gtk_file_system_model_apply_monitor_change (model, + change->file, + change->type); + pending_monitor_change_free (change); + + if ((++processed % MODEL_DRAIN_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + return TRUE; + } + + model->finalize_phase = MODEL_FINALIZE_NONE; + model->drain_source = 0; + + g_signal_emit (model, + file_system_model_signals[FINISHED_LOADING], + 0, model->load_error); + g_clear_error (&model->load_error); + + return FALSE; + } + + g_assert_not_reached (); + } +} + + G_DEFINE_TYPE_WITH_CODE (GtkFileSystemModel, _gtk_file_system_model, G_TYPE_OBJECT, @@ -1038,16 +1444,23 @@ gtk_file_system_model_dispose (GObject *object) { GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object); - if (model->dir_thaw_source) + if (model->drain_source != 0) { - g_source_remove (model->dir_thaw_source); - model->dir_thaw_source = 0; + g_source_remove (model->drain_source); + model->drain_source = 0; } g_cancellable_cancel (model->cancellable); + g_clear_pointer (&model->pending_monitor_queries, g_hash_table_destroy); + if (model->dir_monitor) g_file_monitor_cancel (model->dir_monitor); + g_list_free_full (model->pending_files, g_object_unref); + model->pending_files = NULL; + + g_clear_object (&model->dir_enumerator); + G_OBJECT_CLASS (_gtk_file_system_model_parent_class)->dispose (object); } @@ -1058,35 +1471,41 @@ gtk_file_system_model_finalize (GObject *object) GtkFileSystemModel *model = GTK_FILE_SYSTEM_MODEL (object); guint i; - for (i = 0; i < model->files->len; i++) + if (model->value_pages) { - int v; - - FileModelNode *node = get_node (model, i); - if (node->file) - g_object_unref (node->file); - if (node->info) - g_object_unref (node->info); - - for (v = 0; v < model->n_columns; v++) - if (G_VALUE_TYPE (&node->values[v]) != G_TYPE_INVALID) - g_value_unset (&node->values[v]); + for (i = 0; i < model->n_columns; i++) + if (model->value_pages[i]) + g_ptr_array_free (model->value_pages[i], TRUE); + g_free (model->value_pages); } - g_array_free (model->files, TRUE); + + g_clear_pointer (&model->files, g_array_unref); + + g_clear_pointer (&model->sort_scratch, g_array_unref); + g_clear_error (&model->load_error); + if (model->pending_monitor_changes) + g_queue_free_full (model->pending_monitor_changes, + pending_monitor_change_free); + + g_clear_pointer (&model->order, g_array_unref); + g_clear_pointer (&model->visible_order, g_array_unref); g_object_unref (model->cancellable); g_free (model->attributes); + if (model->dir) g_object_unref (model->dir); if (model->dir_monitor) g_object_unref (model->dir_monitor); + g_hash_table_destroy (model->file_lookup); + if (model->filter) g_object_unref (model->filter); g_slice_free1 (sizeof (GType) * model->n_columns, model->column_types); - _gtk_tree_data_list_header_free (model->sort_list); + if (model->default_sort_destroy) model->default_sort_destroy (model->default_sort_data); @@ -1133,95 +1552,113 @@ gtk_file_system_model_closed_enumerator (GObject *object, GAsyncResult *res, gpo g_file_enumerator_close_finish (G_FILE_ENUMERATOR (object), res, NULL); } + +static void +gtk_file_system_model_request_next_files (GtkFileSystemModel *model) +{ + if (model->dir_enumerator == NULL || + g_cancellable_is_cancelled (model->cancellable)) + return; + + g_file_enumerator_next_files_async (model->dir_enumerator, + FILES_PER_QUERY, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_got_files, + model); +} + static gboolean -thaw_func (gpointer data) +gtk_file_system_model_drain (gpointer data) { GtkFileSystemModel *model = data; + gint64 deadline; + guint processed = 0; - thaw_updates (model); - model->dir_thaw_source = 0; + if (model->editing_hold) + { + model->drain_source = 0; + return FALSE; + } + deadline = g_get_monotonic_time () + MODEL_DRAIN_SLICE_US; + + while (model->pending_files != NULL) + { + GList *link = model->pending_files; + GFileInfo *info = link->data; + const char *name; + + model->pending_files = + g_list_delete_link (model->pending_files, link); + + name = g_file_info_get_name (info); + if (name != NULL) + { + GFile *file = g_file_get_child (model->dir, name); + add_file (model, file, info); + g_object_unref (file); + } + + g_object_unref (info); + processed++; + + if ((processed % MODEL_DRAIN_CLOCK_GRANULARITY) == 0 && + g_get_monotonic_time () >= deadline) + break; + } + + if (model->pending_files != NULL) + return TRUE; + + model->drain_source = 0; + gtk_file_system_model_request_next_files (model); return FALSE; } static void -gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer data) +gtk_file_system_model_got_files (GObject *object, + GAsyncResult *res, + gpointer data) { GFileEnumerator *enumerator = G_FILE_ENUMERATOR (object); - GtkFileSystemModel *model = data; - GList *walk, *files; + GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ + GList *files; GError *error = NULL; - gdk_threads_enter (); - files = g_file_enumerator_next_files_finish (enumerator, res, &error); - if (files) + if (files != NULL) { - if (model->dir_thaw_source == 0) - { - freeze_updates (model); - model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1, - 50, - thaw_func, - model, - NULL); - } - - for (walk = files; walk; walk = walk->next) - { - const char *name; - GFileInfo *info; - GFile *file; - - info = walk->data; - name = g_file_info_get_name (info); - if (name == NULL) - { - /* Shouldn't happen, but the APIs allow it */ - g_object_unref (info); - continue; - } - file = g_file_get_child (model->dir, name); - add_file (model, file, info); - g_object_unref (file); - g_object_unref (info); - } - g_list_free (files); - - g_file_enumerator_next_files_async (enumerator, - g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_got_files, - model); - } - else - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - { - g_file_enumerator_close_async (enumerator, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_closed_enumerator, - NULL); - if (model->dir_thaw_source != 0) - { - g_source_remove (model->dir_thaw_source); - model->dir_thaw_source = 0; - thaw_updates (model); - } - - g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error); - } - - if (error) - g_error_free (error); + gdk_threads_enter (); + g_assert (model->pending_files == NULL); + model->pending_files = files; + gtk_file_system_model_schedule_work (model); + gdk_threads_leave (); + return; } - gdk_threads_leave (); + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + gdk_threads_enter (); + + g_file_enumerator_close_async (enumerator, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_closed_enumerator, + NULL); + + g_clear_object (&model->dir_enumerator); + + gtk_file_system_model_begin_finalize_load (model, error); + gdk_threads_leave (); + } + + if (error) + g_error_free (error); } + static void gtk_file_system_model_query_done (GObject * object, GAsyncResult *res, @@ -1230,7 +1667,7 @@ gtk_file_system_model_query_done (GObject * object, GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ GFile *file = G_FILE (object); GFileInfo *info; - guint id; + gboolean file_known; info = g_file_query_info_finish (file, res, NULL); if (info == NULL) @@ -1238,22 +1675,57 @@ gtk_file_system_model_query_done (GObject * object, gdk_threads_enter (); + file_known = node_get_for_file (model, file) != 0; + _gtk_file_system_model_update_file (model, file, info); - id = node_get_for_file (model, file); - gtk_file_system_model_sort_node (model, id); + if (file_known) + gtk_file_system_model_sort (model); g_object_unref (info); gdk_threads_leave (); } + static void -gtk_file_system_model_monitor_change (GFileMonitor * monitor, - GFile * file, - GFile * other_file, - GFileMonitorEvent type, - GtkFileSystemModel *model) +gtk_file_system_model_monitor_query_done (GObject *object, + GAsyncResult *res, + gpointer data) +{ + PendingMonitorQuery *query = data; + GtkFileSystemModel *model = g_weak_ref_get (query); + GFileInfo *info = g_file_query_info_finish (G_FILE (object), res, NULL); + + if (model) + { + gdk_threads_enter (); + + if (model->pending_monitor_queries && + g_hash_table_lookup (model->pending_monitor_queries, + G_FILE (object)) == query) + { + g_hash_table_remove (model->pending_monitor_queries, + G_FILE (object)); + if (info) + _gtk_file_system_model_update_file (model, G_FILE (object), info); + } + + gdk_threads_leave (); + g_object_unref (model); + } + + g_clear_object (&info); + + g_weak_ref_clear (query); + g_free (query); +} + + +static void +gtk_file_system_model_apply_monitor_change (GtkFileSystemModel *model, + GFile *file, + GFileMonitorEvent type) { switch (type) { @@ -1261,21 +1733,43 @@ gtk_file_system_model_monitor_change (GFileMonitor * monitor, case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: /* We can treat all of these the same way */ - g_file_query_info_async (file, - model->attributes, - G_FILE_QUERY_INFO_NONE, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_query_done, - model); + { + PendingMonitorQuery *query; + + query = g_new (PendingMonitorQuery, 1); + g_weak_ref_init (query, G_OBJECT (model)); + + gdk_threads_enter (); + + if (model->pending_monitor_queries == NULL) + model->pending_monitor_queries = + g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + (GDestroyNotify) g_object_unref, NULL); + + g_hash_table_replace (model->pending_monitor_queries, + g_object_ref (file), query); + + gdk_threads_leave (); + + g_file_query_info_async (file, + model->attributes, + G_FILE_QUERY_INFO_NONE, + IO_PRIORITY, + model->cancellable, + gtk_file_system_model_monitor_query_done, + query); + } break; + case G_FILE_MONITOR_EVENT_DELETED: - gdk_threads_enter (); + gdk_threads_enter (); + if (model->pending_monitor_queries) + g_hash_table_remove (model->pending_monitor_queries, file); remove_file (model, file); - gdk_threads_leave (); + gdk_threads_leave (); break; + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: - /* FIXME: use freeze/thaw with this somehow? */ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: case G_FILE_MONITOR_EVENT_UNMOUNTED: default: @@ -1285,46 +1779,73 @@ gtk_file_system_model_monitor_change (GFileMonitor * monitor, } static void -gtk_file_system_model_got_enumerator (GObject *dir, GAsyncResult *res, gpointer data) +gtk_file_system_model_monitor_change (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent type, + GtkFileSystemModel *model) { - GtkFileSystemModel *model = data; + if (model->directory_loading || + model->drain_source != 0) + { + PendingMonitorChange *change; + + change = g_new (PendingMonitorChange, 1); + change->file = g_object_ref (file); + change->type = type; + + if (model->pending_monitor_changes == NULL) + model->pending_monitor_changes = g_queue_new (); + + g_queue_push_tail (model->pending_monitor_changes, change); + return; + } + + gtk_file_system_model_apply_monitor_change (model, file, type); +} + +static void +gtk_file_system_model_got_enumerator (GObject *object, + GAsyncResult *res, + gpointer data) +{ + GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ GFileEnumerator *enumerator; GError *error = NULL; - gdk_threads_enter (); - - enumerator = g_file_enumerate_children_finish (G_FILE (dir), res, &error); + enumerator = g_file_enumerate_children_finish (G_FILE (object), res, &error); if (enumerator == NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - { - g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error); + { + gdk_threads_enter (); + gtk_file_system_model_begin_finalize_load (model, error); + gdk_threads_leave (); + } + + if (error) g_error_free (error); - } - } - else - { - g_file_enumerator_next_files_async (enumerator, - g_file_is_native (model->dir) ? 50 * FILES_PER_QUERY : FILES_PER_QUERY, - IO_PRIORITY, - model->cancellable, - gtk_file_system_model_got_files, - model); - g_object_unref (enumerator); - model->dir_monitor = g_file_monitor_directory (model->dir, - G_FILE_MONITOR_NONE, - model->cancellable, - NULL); /* we don't mind if directory monitoring isn't supported, so the GError is NULL here */ - if (model->dir_monitor) - g_signal_connect (model->dir_monitor, - "changed", - G_CALLBACK (gtk_file_system_model_monitor_change), - model); + return; } + gdk_threads_enter (); + + g_assert (model->dir_enumerator == NULL); + model->dir_enumerator = enumerator; + + model->dir_monitor = g_file_monitor_directory (model->dir, + G_FILE_MONITOR_NONE, + model->cancellable, + NULL); + if (model->dir_monitor) + g_signal_connect (model->dir_monitor, "changed", + G_CALLBACK (gtk_file_system_model_monitor_change), model); + + gtk_file_system_model_request_next_files (model); gdk_threads_leave (); } + static void gtk_file_system_model_set_n_columns (GtkFileSystemModel *model, gint n_columns, @@ -1337,27 +1858,34 @@ gtk_file_system_model_set_n_columns (GtkFileSystemModel *model, model->n_columns = n_columns; model->column_types = g_slice_alloc (sizeof (GType) * n_columns); - - model->node_size = sizeof (FileModelNode) + sizeof (GValue) * (n_columns - 1); /* minus 1 because FileModelNode.values[] has a default size of 1 */ + model->value_pages = g_new0 (GPtrArray *, n_columns); for (i = 0; i < (guint) n_columns; i++) { GType type = va_arg (args, GType); - if (! _gtk_tree_data_list_check_type (type)) - { - g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", G_STRLOC, g_type_name (type)); - return; /* not reached */ - } + if (!_gtk_tree_data_list_check_type (type)) + { + g_error ("%s: type %s cannot be a column type for GtkFileSystemModel\n", + G_STRLOC, g_type_name (type)); + return; + } model->column_types[i] = type; } - model->sort_list = _gtk_tree_data_list_header_new (n_columns, model->column_types); + model->sort_list = + _gtk_tree_data_list_header_new (n_columns, model->column_types); - model->files = g_array_sized_new (FALSE, FALSE, model->node_size, FILES_PER_QUERY); - /* add editable node at start */ + model->files = + g_array_sized_new (FALSE, TRUE, sizeof (FileModelNode), FILES_PER_QUERY); + g_array_set_clear_func (model->files, file_model_node_clear); + model->order = + g_array_sized_new (FALSE, FALSE, sizeof (guint), FILES_PER_QUERY); + model->visible_order = + g_array_sized_new (FALSE, FALSE, sizeof (guint), FILES_PER_QUERY); g_array_set_size (model->files, 1); - memset (get_node (model, 0), 0, model->node_size); + + get_node (model, 0)->generation = 1; } static void @@ -1369,6 +1897,7 @@ gtk_file_system_model_set_directory (GtkFileSystemModel *model, model->dir = g_object_ref (dir); model->attributes = g_strdup (attributes); + model->directory_loading = TRUE; g_file_enumerate_children_async (model->dir, attributes, @@ -1476,22 +2005,22 @@ gtk_file_system_model_refilter_all (GtkFileSystemModel *model) { guint i; - if (model->frozen) + if (model->directory_loading || model->editing_hold) { - model->filter_on_thaw = TRUE; + model->refilter_pending = TRUE; return; } - freeze_updates (model); + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + node_compute_visibility_and_filters (model, id); + } - /* start at index 1, don't change the editable */ - for (i = 1; i < model->files->len; i++) - node_compute_visibility_and_filters (model, i); - - model->filter_on_thaw = FALSE; - thaw_updates (model); + model->refilter_pending = FALSE; } + /** * _gtk_file_system_model_set_show_hidden: * @model: a #GtkFileSystemModel @@ -1620,12 +2149,13 @@ _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model) **/ gboolean _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE); g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); node = get_node (model, ITER_INDEX (iter)); return node->visible; @@ -1646,12 +2176,13 @@ _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, **/ gboolean _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE); g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (ITER_IS_VALID (model, iter), FALSE); node = get_node (model, ITER_INDEX (iter)); return node->filtered_out; @@ -1676,12 +2207,13 @@ _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, **/ GFileInfo * _gtk_file_system_model_get_info (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); node = get_node (model, ITER_INDEX (iter)); g_assert (node->info == NULL || G_IS_FILE_INFO (node->info)); @@ -1702,11 +2234,13 @@ _gtk_file_system_model_get_info (GtkFileSystemModel *model, **/ GFile * _gtk_file_system_model_get_file (GtkFileSystemModel *model, - GtkTreeIter *iter) + GtkTreeIter *iter) { FileModelNode *node; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); node = get_node (model, ITER_INDEX (iter)); return node->file; @@ -1731,63 +2265,43 @@ _gtk_file_system_model_get_file (GtkFileSystemModel *model, **/ const GValue * _gtk_file_system_model_get_value (GtkFileSystemModel *model, - GtkTreeIter * iter, + GtkTreeIter *iter, int column) { FileModelNode *node; + GValue *value; + guint id; g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), NULL); - g_return_val_if_fail (column >= 0 && (guint) column < model->n_columns, NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); + g_return_val_if_fail (column >= 0 && + (guint) column < model->n_columns, NULL); - node = get_node (model, ITER_INDEX (iter)); + id = ITER_INDEX (iter); + node = get_node (model, id); + value = value_cache_slot (model, id, column, TRUE); - if (!G_VALUE_TYPE (&node->values[column])) + if (G_VALUE_TYPE (value) == G_TYPE_INVALID) { - g_value_init (&node->values[column], model->column_types[column]); - if (!model->get_func (model, - node->file, - node->info, - column, - &node->values[column], - model->get_data)) + g_value_init (value, model->column_types[column]); + + if (!model->get_func (model, node->file, node->info, column, + value, model->get_data)) { - g_value_unset (&node->values[column]); + g_value_unset (value); return NULL; } } - return &node->values[column]; + return value; } static guint node_get_for_file (GtkFileSystemModel *model, - GFile * file) + GFile *file) { - guint i; - - i = GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file)); - if (i != 0) - return i; - - /* Node 0 is the editable row and has no associated file or entry in the table, so we start counting from 1. - * - * The invariant here is that the files in model->files[n] for n < g_hash_table_size (model->file_lookup) - * are already added to the hash table. The table can get cleared when we re-sort; this loop merely rebuilds - * our (file -> index) mapping on demand. - * - * If we exit the loop, the next pending batch of mappings will be resolved when this function gets called again - * with another file that is not yet in the mapping. - */ - for (i = g_hash_table_size (model->file_lookup) + 1; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - - g_hash_table_insert (model->file_lookup, node->file, GUINT_TO_POINTER (i)); - if (g_file_equal (node->file, file)) - return i; - } - - return 0; + return GPOINTER_TO_UINT (g_hash_table_lookup (model->file_lookup, file)); } /** @@ -1830,26 +2344,6 @@ _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model, * if the index is equal or after the specified id. We use this to slide the * mappings up or down when a node is added or removed, respectively. */ -static void -adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment) -{ - GHashTableIter iter; - gpointer key; - gpointer value; - - g_hash_table_iter_init (&iter, model->file_lookup); - - while (g_hash_table_iter_next (&iter, &key, &value)) - { - guint index = GPOINTER_TO_UINT (value); - - if (index >= id) - { - index += increment; - g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (index)); - } - } -} /** * add_file: @@ -1862,30 +2356,71 @@ adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment) **/ static void add_file (GtkFileSystemModel *model, - GFile *file, - GFileInfo *info) + GFile *file, + GFileInfo *info) { FileModelNode *node; + guint id; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (G_IS_FILE (file)); g_return_if_fail (G_IS_FILE_INFO (info)); - node = g_slice_alloc0 (model->node_size); + if (node_get_for_file (model, file) != 0) + return; + + id = node_allocate_slot (model); + node = get_node (model, id); node->file = g_object_ref (file); - if (info) - node->info = g_object_ref (info); - node->frozen_add = model->frozen ? TRUE : FALSE; + node->info = g_object_ref (info); - g_array_append_vals (model->files, node, 1); - g_slice_free1 (model->node_size, node); + g_hash_table_insert (model->file_lookup, + node->file, GUINT_TO_POINTER (id)); - if (!model->frozen) - node_compute_visibility_and_filters (model, model->files->len -1); + g_array_append_val (model->order, id); + node->order_pos = model->order->len - 1; - gtk_file_system_model_sort_node (model, model->files->len -1); + if (model->directory_loading) + return; + + if (model->editing_hold) + { + node->held_add = TRUE; + model->resort_pending = TRUE; + return; + } + + { + SortData data; + guint lo = 0; + guint hi = node->order_pos; + + if (sort_data_init (&data, model)) + { + while (lo < hi) + { + guint mid = lo + (hi - lo) / 2; + guint mid_id = g_array_index (model->order, guint, mid); + int cmp = compare_order_element (&id, &mid_id, &data); + + if (cmp < 0) + hi = mid; + else + lo = mid + 1; + } + + if (lo != node->order_pos) + { + g_array_remove_index (model->order, node->order_pos); + g_array_insert_val (model->order, lo, id); + order_update_positions_from (model, lo); + } + } + } + node_compute_visibility_and_filters (model, id); } + /** * remove_file: * @model: the model @@ -1897,12 +2432,12 @@ add_file (GtkFileSystemModel *model, **/ static void remove_file (GtkFileSystemModel *model, - GFile *file) + GFile *file) { FileModelNode *node; gboolean was_visible; + guint row = G_MAXUINT; guint id; - guint row; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (G_IS_FILE (file)); @@ -1913,20 +2448,22 @@ remove_file (GtkFileSystemModel *model, node = get_node (model, id); was_visible = node->visible; - row = node_get_tree_row (model, id); - node_invalidate_index (model, id); + if (was_visible) + { + row = node->visible_pos; + visible_remove_node (model, id); + } + g_assert (id != 0); + { + guint pos = node->order_pos; + + g_array_remove_index (model->order, pos); + order_update_positions_from (model, pos); + } g_hash_table_remove (model->file_lookup, file); - g_object_unref (node->file); - adjust_file_lookup (model, id, -1); - - if (node->info) - g_object_unref (node->info); - - g_array_remove_index (model->files, id); - - /* We don't need to resort, as removing a row doesn't change the sorting order of the other rows */ + node_retire_slot (model, id); if (was_visible) emit_row_deleted_for_row (model, row); @@ -1948,7 +2485,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, GFileInfo *info) { FileModelNode *node; - guint i, id; + guint id; GFileInfo *old_info; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); @@ -1959,7 +2496,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, if (id == 0) { add_file (model, file, info); - id = node_get_for_file (model, file); + return; } node = get_node (model, id); @@ -1970,11 +2507,7 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, if (old_info) g_object_unref (old_info); - for (i = 0; i < model->n_columns; i++) - { - if (G_VALUE_TYPE (&node->values[i])) - g_value_unset (&node->values[i]); - } + value_cache_clear_node (model, id); if (node->visible) emit_row_changed_for_node (model, id); @@ -2010,62 +2543,6 @@ _gtk_file_system_model_set_filter (GtkFileSystemModel *model, gtk_file_system_model_refilter_all (model); } -/** - * freeze_updates: - * @model: a #GtkFileSystemModel - * - * Freezes most updates on the model, so that performing multiple operations on - * the files in the model do not cause any events. Use thaw_updates() to resume - * proper operations. It is fine to call this function multiple times as long as - * freeze and thaw calls are balanced. - **/ -static void -freeze_updates (GtkFileSystemModel *model) -{ - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - - model->frozen++; -} - -/** - * thaw_updates: - * @model: a #GtkFileSystemModel - * - * Undoes the effect of a previous call to freeze_updates() - **/ -static void -thaw_updates (GtkFileSystemModel *model) -{ - gboolean stuff_added; - - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (model->frozen > 0); - - model->frozen--; - if (model->frozen > 0) - return; - - stuff_added = get_node (model, model->files->len - 1)->frozen_add; - - if (model->filter_on_thaw) - gtk_file_system_model_refilter_all (model); - if (model->sort_on_thaw) - gtk_file_system_model_sort (model); - if (stuff_added) - { - guint i; - - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); - - if (!node->frozen_add) - continue; - node->frozen_add = FALSE; - node_compute_visibility_and_filters (model, i); - } - } -} /** * _gtk_file_system_model_clear_cache: @@ -2080,16 +2557,17 @@ thaw_updates (GtkFileSystemModel *model) **/ void _gtk_file_system_model_clear_cache (GtkFileSystemModel *model, - int column) + int column) { - guint i; + guint8 *changed; int start, end; - gboolean changed; + int c; + guint i; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (column >= -1 && (guint) column < model->n_columns); - if (column > -1) + if (column >= 0) { start = column; end = column + 1; @@ -2100,42 +2578,75 @@ _gtk_file_system_model_clear_cache (GtkFileSystemModel *model, end = model->n_columns; } - for (i = 0; i < model->files->len; i++) - { - FileModelNode *node = get_node (model, i); + changed = g_new0 (guint8, model->files->len); - changed = FALSE; - for (column = start; column < end; column++) + for (c = start; c < end; c++) + { + GPtrArray *pages = model->value_pages[c]; + guint page_index; + + if (pages == NULL) + continue; + + for (page_index = 0; page_index < pages->len; page_index++) { - if (!G_VALUE_TYPE (&node->values[column])) + ValuePage *page = pages->pdata[page_index]; + guint slot; + guint base; + + if (page == NULL) continue; - g_value_unset (&node->values[column]); - changed = TRUE; - } + base = page_index << VALUE_PAGE_SHIFT; + for (slot = 0; slot < VALUE_PAGE_SIZE; slot++) + { + guint id = base + slot; + GValue *value; - if (changed && node->visible) - emit_row_changed_for_node (model, i); + if (id >= model->files->len) + break; + + value = &page->values[slot]; + if (G_VALUE_TYPE (value) == G_TYPE_INVALID) + continue; + + g_value_unset (value); + changed[id] = TRUE; + } + } } + /* Emit at most one change signal for each visible row, in row order. */ + for (i = 0; i < model->visible_order->len; i++) + { + guint id = g_array_index (model->visible_order, guint, i); + if (changed[id]) + emit_row_changed_for_node (model, id); + } + + g_free (changed); + /* FIXME: resort? */ } + void _gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model, - GtkTreeIter *iter, - int column) + GtkTreeIter *iter, + int column) { FileModelNode *node; - int start, end; gboolean changed = FALSE; + guint id; + int start, end; + int c; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (iter != NULL); g_return_if_fail (ITER_IS_VALID (model, iter)); g_return_if_fail (column >= -1 && (guint) column < model->n_columns); - if (column > -1) + if (column >= 0) { start = column; end = column + 1; @@ -2146,19 +2657,14 @@ _gtk_file_system_model_clear_cache_for_iter (GtkFileSystemModel *model, end = model->n_columns; } - node = get_node (model, ITER_INDEX (iter)); + id = ITER_INDEX (iter); + node = get_node (model, id); - for (column = start; column < end; column++) - { - if (!G_VALUE_TYPE (&node->values[column])) - continue; - - g_value_unset (&node->values[column]); - changed = TRUE; - } + for (c = start; c < end; c++) + changed |= value_cache_clear_slot (model, id, c); if (changed && node->visible) - emit_row_changed_for_node (model, ITER_INDEX (iter)); + emit_row_changed_for_node (model, id); } /** @@ -2200,20 +2706,26 @@ _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model, * using this temporary row, call _gtk_file_system_model_remove_editable(). **/ void -_gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *iter) +_gtk_file_system_model_add_editable (GtkFileSystemModel *model, + GtkTreeIter *iter) { g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + g_return_if_fail (iter != NULL); g_return_if_fail (!get_node (model, 0)->visible); node_set_visible_and_filtered_out (model, 0, TRUE, FALSE); ITER_INIT_FROM_INDEX (model, iter, 0); - /* we don't want file system changes to affect the model while - * editing is in place - */ - freeze_updates (model); + model->editing_hold = TRUE; + + if (model->directory_loading && model->drain_source != 0) + { + g_source_remove (model->drain_source); + model->drain_source = 0; + } } + /** * _gtk_file_system_model_remove_editable: * @model: a #GtkFileSystemModel @@ -2225,10 +2737,32 @@ _gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *ite void _gtk_file_system_model_remove_editable (GtkFileSystemModel *model) { + guint i; + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (get_node (model, 0)->visible); + g_return_if_fail (model->editing_hold); - thaw_updates (model); + model->editing_hold = FALSE; - node_set_visible_and_filtered_out (model, 0, FALSE, FALSE); + if (model->resort_pending) + gtk_file_system_model_sort (model); + if (model->refilter_pending) + gtk_file_system_model_refilter_all (model); + + for (i = 0; i < model->order->len; i++) + { + guint id = g_array_index (model->order, guint, i); + FileModelNode *node = get_node (model, id); + + if (!node->held_add) + continue; + + node->held_add = FALSE; + node_compute_visibility_and_filters (model, id); + } + + if (get_node (model, 0)->visible) + node_set_visible_and_filtered_out (model, 0, FALSE, FALSE); + + gtk_file_system_model_schedule_work (model); } diff --git a/gtk/gtkfilesystemmodel.h b/gtk/gtkfilesystemmodel.h index 4bdda0c23..57853a77c 100644 --- a/gtk/gtkfilesystemmodel.h +++ b/gtk/gtkfilesystemmodel.h @@ -92,7 +92,7 @@ void _gtk_file_system_model_set_filter (GtkFileSystemModel GtkFileFilter *filter); void _gtk_file_system_model_add_editable (GtkFileSystemModel *model, - GtkTreeIter *iter); + GtkTreeIter *iter); void _gtk_file_system_model_remove_editable (GtkFileSystemModel *model); G_END_DECLS diff --git a/gtk/gtkfontbutton.c b/gtk/gtkfontbutton.c index 08aea103e..a52ddd470 100644 --- a/gtk/gtkfontbutton.c +++ b/gtk/gtkfontbutton.c @@ -889,7 +889,7 @@ gtk_font_button_update_font_info (GtkFontButton *font_button) if (style == NULL || !g_ascii_strcasecmp (style, "Regular")) family_style = g_strdup (family); else - family_style = g_strdup_printf ("%s %s", family, style); + family_style = g_strdup_printf ("%s %s", family, _(style)); gtk_label_set_text (GTK_LABEL (font_button->priv->font_label), family_style); diff --git a/gtk/gtkfontsel.c b/gtk/gtkfontsel.c index 701d22841..81daa0fae 100644 --- a/gtk/gtkfontsel.c +++ b/gtk/gtkfontsel.c @@ -76,6 +76,22 @@ #define DEFAULT_FONT_NAME "Sans 10" +/* Common font style names, listed here so they can be translated when + * Pango returns the standard English names. + */ +#define FONT_STYLE_ULTRA_LIGHT N_("Ultra-Light") +#define FONT_STYLE_LIGHT N_("Light") +#define FONT_STYLE_MEDIUM N_("Medium") +#define FONT_STYLE_NORMAL N_("Normal") +#define FONT_STYLE_REGULAR N_("Regular") +#define FONT_STYLE_ITALIC N_("Italic") +#define FONT_STYLE_OBLIQUE N_("Oblique") +#define FONT_STYLE_SEMI_BOLD N_("Semi-Bold") +#define FONT_STYLE_BOLD N_("Bold") +#define FONT_STYLE_ULTRA_BOLD N_("Ultra-Bold") +#define FONT_STYLE_HEAVY N_("Heavy") +#define FONT_STYLE_BOLD_ITALIC N_("Bold Italic") + /* This is the initial and maximum height of the preview entry (it expands when large font sizes are selected). Initial height is also the minimum. */ #define INITIAL_PREVIEW_HEIGHT 44 @@ -699,11 +715,11 @@ gtk_font_selection_scroll_on_map (GtkWidget *widget, } /* This is called when a family is selected in the list. */ -static void -gtk_font_selection_select_font (GtkTreeSelection *selection, - gpointer data) +static gboolean +gtk_font_selection_real_select_font (gpointer data) { GtkFontSelection *fontsel; + GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; #ifdef INCLUDE_FONT_ENTRIES @@ -711,6 +727,7 @@ gtk_font_selection_select_font (GtkTreeSelection *selection, #endif fontsel = GTK_FONT_SELECTION (data); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fontsel->family_list)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { @@ -718,29 +735,53 @@ gtk_font_selection_select_font (GtkTreeSelection *selection, gtk_tree_model_get (model, &iter, FAMILY_COLUMN, &family, -1); if (fontsel->family != family) - { - gtk_font_selection_ref_family (fontsel, family); + { + gtk_font_selection_ref_family (fontsel, family); #ifdef INCLUDE_FONT_ENTRIES - family_name = pango_font_family_get_name (fontsel->family); - gtk_entry_set_text (GTK_ENTRY (fontsel->font_entry), family_name); + family_name = pango_font_family_get_name (fontsel->family); + gtk_entry_set_text (GTK_ENTRY (fontsel->font_entry), family_name); #endif - gtk_font_selection_show_available_styles (fontsel); - gtk_font_selection_select_best_style (fontsel, TRUE); - } + gtk_font_selection_show_available_styles (fontsel); + gtk_font_selection_select_best_style (fontsel, TRUE); + } g_object_unref (family); } + + return FALSE; } -static int -cmp_families (const void *a, const void *b) +static void +gtk_font_selection_select_font (GtkTreeSelection *selection, + gpointer data) { - const char *a_name = pango_font_family_get_name (*(PangoFontFamily **)a); - const char *b_name = pango_font_family_get_name (*(PangoFontFamily **)b); + GtkFontSelection *fontsel = GTK_FONT_SELECTION (data); - return g_utf8_collate (a_name, b_name); + gtk_font_selection_real_select_font (fontsel); +} + +typedef struct +{ + PangoFontFamily *family; + const gchar *name; + gchar *collate_key; +} FontFamilySortInfo; + +static int +cmp_families (const void *a, + const void *b) +{ + const FontFamilySortInfo *family_a = a; + const FontFamilySortInfo *family_b = b; + gint result; + + result = strcmp (family_a->collate_key, family_b->collate_key); + if (result != 0) + return result; + + return g_strcmp0 (family_a->name, family_b->name); } static void @@ -748,6 +789,7 @@ gtk_font_selection_show_available_fonts (GtkFontSelection *fontsel) { GtkListStore *model; PangoFontFamily **families; + FontFamilySortInfo *sorted_families; PangoFontFamily *match_family = NULL; gint n_families, i; GtkTreeIter match_row; @@ -755,27 +797,37 @@ gtk_font_selection_show_available_fonts (GtkFontSelection *fontsel) model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fontsel->family_list))); pango_context_list_families (gtk_widget_get_pango_context (GTK_WIDGET (fontsel)), - &families, &n_families); - qsort (families, n_families, sizeof (PangoFontFamily *), cmp_families); + &families, &n_families); + + sorted_families = g_new (FontFamilySortInfo, n_families); + for (i = 0; i < n_families; i++) + { + const gchar *name = pango_font_family_get_name (families[i]); + + sorted_families[i].family = families[i]; + sorted_families[i].name = name; + sorted_families[i].collate_key = g_utf8_collate_key (name ? name : "", -1); + } + + qsort (sorted_families, n_families, sizeof (FontFamilySortInfo), cmp_families); gtk_list_store_clear (model); - for (i=0; ifamily_list), &match_row); #ifdef INCLUDE_FONT_ENTRIES gtk_entry_set_text (GTK_ENTRY (fontsel->font_entry), - pango_font_family_get_name (match_family)); + pango_font_family_get_name (match_family)); #endif /* INCLUDE_FONT_ENTRIES */ } + for (i = 0; i < n_families; i++) + g_free (sorted_families[i].collate_key); + + g_free (sorted_families); g_free (families); } @@ -813,18 +869,26 @@ compare_font_descriptions (const PangoFontDescription *a, const PangoFontDescrip return 0; } -static int -faces_sort_func (const void *a, const void *b) +typedef struct { - PangoFontDescription *desc_a = pango_font_face_describe (*(PangoFontFace **)a); - PangoFontDescription *desc_b = pango_font_face_describe (*(PangoFontFace **)b); + PangoFontFace *face; + PangoFontDescription *desc; + const gchar *name; +} FontFaceSortInfo; - int ord = compare_font_descriptions (desc_a, desc_b); +static int +faces_sort_func (const void *a, + const void *b) +{ + const FontFaceSortInfo *face_a = a; + const FontFaceSortInfo *face_b = b; + gint result; - pango_font_description_free (desc_a); - pango_font_description_free (desc_b); + result = compare_font_descriptions (face_a->desc, face_b->desc); + if (result != 0) + return result; - return ord; + return g_strcmp0 (face_a->name, face_b->name); } static gboolean @@ -844,6 +908,7 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) { gint n_faces, i; PangoFontFace **faces; + FontFaceSortInfo *sorted_faces; PangoFontDescription *old_desc; GtkListStore *model; GtkTreeIter match_row; @@ -857,38 +922,40 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) old_desc= NULL; pango_font_family_list_faces (fontsel->family, &faces, &n_faces); - qsort (faces, n_faces, sizeof (PangoFontFace *), faces_sort_func); + + sorted_faces = g_new (FontFaceSortInfo, n_faces); + for (i = 0; i < n_faces; i++) + { + sorted_faces[i].face = faces[i]; + sorted_faces[i].desc = pango_font_face_describe (faces[i]); + sorted_faces[i].name = pango_font_face_get_face_name (faces[i]); + } + + qsort (sorted_faces, n_faces, sizeof (FontFaceSortInfo), faces_sort_func); gtk_list_store_clear (model); - for (i=0; i < n_faces; i++) + for (i = 0; i < n_faces; i++) { GtkTreeIter iter; - const gchar *str = pango_font_face_get_face_name (faces[i]); + const gchar *str = sorted_faces[i].name; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, - FACE_COLUMN, faces[i], - FACE_NAME_COLUMN, str, - -1); + gtk_list_store_insert_with_values (model, &iter, -1, + FACE_COLUMN, sorted_faces[i].face, + FACE_NAME_COLUMN, _(str), + -1); if (i == 0) - { - match_row = iter; - match_face = faces[i]; - } - else if (old_desc) - { - PangoFontDescription *tmp_desc = pango_font_face_describe (faces[i]); - - if (font_description_style_equal (tmp_desc, old_desc)) - { - match_row = iter; - match_face = faces[i]; - } - - pango_font_description_free (tmp_desc); - } + { + match_row = iter; + match_face = sorted_faces[i].face; + } + else if (old_desc && + font_description_style_equal (sorted_faces[i].desc, old_desc)) + { + match_row = iter; + match_face = sorted_faces[i].face; + } } if (old_desc) @@ -905,6 +972,10 @@ gtk_font_selection_show_available_styles (GtkFontSelection *fontsel) set_cursor_to_iter (GTK_TREE_VIEW (fontsel->face_list), &match_row); } + for (i = 0; i < n_faces; i++) + pango_font_description_free (sorted_faces[i].desc); + + g_free (sorted_faces); g_free (faces); } @@ -934,14 +1005,16 @@ gtk_font_selection_select_best_style (GtkFontSelection *fontsel, /* This is called when a style is selected in the list. */ -static void -gtk_font_selection_select_style (GtkTreeSelection *selection, - gpointer data) +static gboolean +gtk_font_selection_real_select_style (gpointer data) { GtkFontSelection *fontsel = GTK_FONT_SELECTION (data); + GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fontsel->face_list)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { PangoFontFace *face; @@ -953,6 +1026,17 @@ gtk_font_selection_select_style (GtkTreeSelection *selection, gtk_font_selection_show_available_sizes (fontsel, FALSE); gtk_font_selection_select_best_size (fontsel); + + return FALSE; +} + +static void +gtk_font_selection_select_style (GtkTreeSelection *selection, + gpointer data) +{ + GtkFontSelection *fontsel = GTK_FONT_SELECTION (data); + + gtk_font_selection_real_select_style (fontsel); } static void @@ -975,8 +1059,9 @@ gtk_font_selection_show_available_sizes (GtkFontSelection *fontsel, { GtkTreeIter iter; - gtk_list_store_append (model, &iter); - gtk_list_store_set (model, &iter, SIZE_COLUMN, font_sizes[i], -1); + gtk_list_store_insert_with_values (model, &iter, -1, + SIZE_COLUMN, font_sizes[i], + -1); if (font_sizes[i] * PANGO_SCALE == fontsel->size) set_cursor_to_iter (GTK_TREE_VIEW (fontsel->size_list), &iter); @@ -1085,22 +1170,34 @@ gtk_font_selection_size_focus_out (GtkWidget *w, } /* This is called when a size is selected in the list. */ -static void -gtk_font_selection_select_size (GtkTreeSelection *selection, - gpointer data) +static gboolean +gtk_font_selection_real_select_size (gpointer data) { GtkFontSelection *fontsel; + GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; gint new_size; fontsel = GTK_FONT_SELECTION (data); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fontsel->size_list)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, SIZE_COLUMN, &new_size, -1); gtk_font_selection_set_size (fontsel, new_size * PANGO_SCALE); } + + return FALSE; +} + +static void +gtk_font_selection_select_size (GtkTreeSelection *selection, + gpointer data) +{ + GtkFontSelection *fontsel = GTK_FONT_SELECTION (data); + + gtk_font_selection_real_select_size (fontsel); } static void diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 0d4435c72..f8aaf9a3b 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -2945,6 +2945,10 @@ icon_info_ensure_scale_and_pixbuf (GtkIconInfo *icon_info, if (mime_type && strcmp (mime_type, "image/svg+xml") == 0) is_svg = TRUE; +#ifdef G_OS_WIN32 + else if (content_type && strcmp (content_type, ".svg") == 0) + is_svg = TRUE; +#endif } g_object_unref (file_info); diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c index 618f7132d..71e4fb82a 100644 --- a/gtk/gtkiconview.c +++ b/gtk/gtkiconview.c @@ -120,6 +120,12 @@ struct _GtkIconViewPrivate GtkAdjustment *vadjustment; guint layout_idle_id; + GList *layout_items; + gint layout_y, layout_maximum_width; + gint layout_row, layout_item_width, layout_next_index; + guint layout_in_progress : 1; + guint layout_scanning_width : 1; + guint layout_restart : 1; gboolean doing_rubberband; gint rubberband_x1, rubberband_y1; @@ -304,6 +310,7 @@ static void gtk_icon_view_queue_draw_path (GtkIco GtkTreePath *path); static void gtk_icon_view_queue_draw_item (GtkIconView *icon_view, GtkIconViewItem *item); +static gboolean layout_callback (gpointer user_data); static void gtk_icon_view_queue_layout (GtkIconView *icon_view); static void gtk_icon_view_set_cursor_item (GtkIconView *icon_view, GtkIconViewItem *item, @@ -1491,6 +1498,8 @@ gtk_icon_view_size_allocate (GtkWidget *widget, GtkIconView *icon_view = GTK_ICON_VIEW (widget); GtkAdjustment *hadjustment, *vadjustment; + gboolean relayout = (icon_view->priv->layout_idle_id != 0 || + widget->allocation.width != allocation->width); widget->allocation = *allocation; @@ -1504,7 +1513,8 @@ gtk_icon_view_size_allocate (GtkWidget *widget, MAX (icon_view->priv->height, allocation->height)); } - gtk_icon_view_layout (icon_view); + if (relayout) + gtk_icon_view_layout (icon_view); gtk_icon_view_allocate_children (icon_view); @@ -1588,6 +1598,10 @@ gtk_icon_view_expose (GtkWidget *widget, for (icons = icon_view->priv->items; icons; icons = icons->next) { GtkIconViewItem *item = icons->data; + + if (icon_view->priv->layout_in_progress && + item->index >= icon_view->priv->layout_next_index) + break; GdkRectangle area; area.x = item->x; @@ -2810,11 +2824,8 @@ gtk_icon_view_set_adjustment_upper (GtkAdjustment *adj, static void gtk_icon_view_layout (GtkIconView *icon_view) { - gint y = 0, maximum_width = 0; - GList *icons; GtkWidget *widget; - gint row; - gint item_width; + gint64 deadline; if (icon_view->priv->layout_idle_id != 0) { @@ -2823,54 +2834,100 @@ gtk_icon_view_layout (GtkIconView *icon_view) } if (icon_view->priv->model == NULL) - return; + { + icon_view->priv->layout_in_progress = FALSE; + icon_view->priv->layout_items = NULL; + return; + } widget = GTK_WIDGET (icon_view); + deadline = g_get_monotonic_time () + 2000; - item_width = icon_view->priv->item_width; - - if (item_width < 0) + if (!icon_view->priv->layout_in_progress || + icon_view->priv->layout_restart) { - for (icons = icon_view->priv->items; icons; icons = icons->next) - { - GtkIconViewItem *item = icons->data; - gtk_icon_view_calculate_item_size (icon_view, item); - item_width = MAX (item_width, item->width); - } + icon_view->priv->layout_in_progress = TRUE; + icon_view->priv->layout_restart = FALSE; + icon_view->priv->layout_items = icon_view->priv->items; + icon_view->priv->layout_y = icon_view->priv->margin; + icon_view->priv->layout_maximum_width = 0; + icon_view->priv->layout_row = 0; + icon_view->priv->layout_item_width = icon_view->priv->item_width; + icon_view->priv->layout_next_index = 0; + icon_view->priv->layout_scanning_width = + icon_view->priv->layout_item_width < 0; + + if (!icon_view->priv->layout_scanning_width && + icon_view->priv->layout_items) + { + gtk_icon_view_set_cell_data (icon_view, + icon_view->priv->layout_items->data); + adjust_wrap_width (icon_view, icon_view->priv->layout_items->data); + } } - - icons = icon_view->priv->items; - y += icon_view->priv->margin; - row = 0; - - if (icons) + while (icon_view->priv->layout_scanning_width && + icon_view->priv->layout_items) { - gtk_icon_view_set_cell_data (icon_view, icons->data); - adjust_wrap_width (icon_view, icons->data); + GtkIconViewItem *item = icon_view->priv->layout_items->data; + + gtk_icon_view_calculate_item_size (icon_view, item); + icon_view->priv->layout_item_width = + MAX (icon_view->priv->layout_item_width, item->width); + icon_view->priv->layout_items = icon_view->priv->layout_items->next; + + if (g_get_monotonic_time () >= deadline) + goto yield; } - do + if (icon_view->priv->layout_scanning_width) { - icons = gtk_icon_view_layout_single_row (icon_view, icons, - item_width, row, - &y, &maximum_width); - row++; + icon_view->priv->layout_scanning_width = FALSE; + icon_view->priv->layout_items = icon_view->priv->items; + + if (icon_view->priv->layout_items) + { + gtk_icon_view_set_cell_data (icon_view, + icon_view->priv->layout_items->data); + adjust_wrap_width (icon_view, icon_view->priv->layout_items->data); + } } - while (icons != NULL); - if (maximum_width != icon_view->priv->width) - icon_view->priv->width = maximum_width; + while (icon_view->priv->layout_items) + { + icon_view->priv->layout_items = + gtk_icon_view_layout_single_row (icon_view, + icon_view->priv->layout_items, + icon_view->priv->layout_item_width, + icon_view->priv->layout_row++, + &icon_view->priv->layout_y, + &icon_view->priv->layout_maximum_width); - y += icon_view->priv->margin; + icon_view->priv->layout_next_index = + icon_view->priv->layout_items + ? ((GtkIconViewItem *) icon_view->priv->layout_items->data)->index + : G_MAXINT; - if (y != icon_view->priv->height) - icon_view->priv->height = y; + if (icon_view->priv->layout_restart || + (icon_view->priv->layout_items && + g_get_monotonic_time () >= deadline)) + goto yield; + } + + icon_view->priv->layout_in_progress = FALSE; + + if (icon_view->priv->layout_maximum_width != icon_view->priv->width) + icon_view->priv->width = icon_view->priv->layout_maximum_width; + + icon_view->priv->layout_y += icon_view->priv->margin; + + if (icon_view->priv->layout_y != icon_view->priv->height) + icon_view->priv->height = icon_view->priv->layout_y; gtk_icon_view_set_adjustment_upper (icon_view->priv->hadjustment, - icon_view->priv->width); + icon_view->priv->width); gtk_icon_view_set_adjustment_upper (icon_view->priv->vadjustment, - icon_view->priv->height); + icon_view->priv->height); if (icon_view->priv->width != widget->requisition.width || icon_view->priv->height != widget->requisition.height) @@ -2878,8 +2935,8 @@ gtk_icon_view_layout (GtkIconView *icon_view) if (gtk_widget_get_realized (GTK_WIDGET (icon_view))) gdk_window_resize (icon_view->priv->bin_window, - MAX (icon_view->priv->width, widget->allocation.width), - MAX (icon_view->priv->height, widget->allocation.height)); + MAX (icon_view->priv->width, widget->allocation.width), + MAX (icon_view->priv->height, widget->allocation.height)); if (icon_view->priv->scroll_to_path) { @@ -2890,13 +2947,21 @@ gtk_icon_view_layout (GtkIconView *icon_view) icon_view->priv->scroll_to_path = NULL; gtk_icon_view_scroll_to_path (icon_view, path, - icon_view->priv->scroll_to_use_align, - icon_view->priv->scroll_to_row_align, - icon_view->priv->scroll_to_col_align); + icon_view->priv->scroll_to_use_align, + icon_view->priv->scroll_to_row_align, + icon_view->priv->scroll_to_col_align); gtk_tree_path_free (path); } gtk_widget_queue_draw (widget); + return; + +yield: + if (icon_view->priv->layout_idle_id == 0) + icon_view->priv->layout_idle_id = + gdk_threads_add_idle (layout_callback, icon_view); + + gtk_widget_queue_draw (widget); } static void @@ -3409,6 +3474,9 @@ layout_callback (gpointer user_data) static void gtk_icon_view_queue_layout (GtkIconView *icon_view) { + if (icon_view->priv->layout_in_progress) + icon_view->priv->layout_restart = TRUE; + if (icon_view->priv->layout_idle_id != 0) return; @@ -3500,6 +3568,10 @@ gtk_icon_view_get_item_at_coords (GtkIconView *icon_view, { GtkIconViewItem *item = items->data; + if (icon_view->priv->layout_in_progress && + item->index >= icon_view->priv->layout_next_index) + break; + if (x >= item->x - icon_view->priv->column_spacing/2 && x <= item->x + item->width + icon_view->priv->column_spacing/2 && y >= item->y - icon_view->priv->row_spacing/2 && y <= item->y + item->height + icon_view->priv->row_spacing/2) { @@ -5207,6 +5279,10 @@ gtk_icon_view_get_visible_range (GtkIconView *icon_view, { GtkIconViewItem *item = icons->data; + if (icon_view->priv->layout_in_progress && + item->index >= icon_view->priv->layout_next_index) + break; + if ((item->x + item->width >= (int)icon_view->priv->hadjustment->value) && (item->y + item->height >= (int)icon_view->priv->vadjustment->value) && (item->x <= (int) (icon_view->priv->hadjustment->value + icon_view->priv->hadjustment->page_size)) && @@ -7917,7 +7993,7 @@ gtk_icon_view_item_accessible_image_get_image_position (AtkImage *image, if (atk_state_set_contains_state (item->state_set, ATK_STATE_DEFUNCT)) return; - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); if (get_pixbuf_box (GTK_ICON_VIEW (item->widget), item->item, &box)) { @@ -8601,7 +8677,7 @@ gtk_icon_view_item_accessible_get_extents (AtkComponent *component, if (gtk_icon_view_item_accessible_is_showing (item)) { parent_obj = gtk_widget_get_accessible (item->widget); - atk_component_get_position (ATK_COMPONENT (parent_obj), &l_x, &l_y, coord_type); + atk_component_get_extents (ATK_COMPONENT (parent_obj), &l_x, &l_y, NULL, NULL, coord_type); *x = l_x + item->item->x; *y = l_y + item->item->y; } diff --git a/gtk/gtkimmodule.c b/gtk/gtkimmodule.c index a3a77c474..46b155901 100644 --- a/gtk/gtkimmodule.c +++ b/gtk/gtkimmodule.c @@ -79,7 +79,7 @@ struct _GtkIMModule GModule *library; void (*list) (const GtkIMContextInfo ***contexts, - guint *n_contexts); + guint *n_contexts); void (*init) (GTypeModule *module); void (*exit) (void); GtkIMContext *(*create) (const gchar *context_id); @@ -309,6 +309,176 @@ add_builtin_module (const gchar *module_name, return module; } +/* Parser helpers adapted from GTK 3.24 gtkutils.c + * (gtk_read_line(), gtk_skip_space(), gtk_scan_string()). + * GTK 3 notes that these helpers were copied from pango-utils.c. + * + * Keep these helpers local to avoid depending on deprecated Pango parser + * utilities while preserving the old gtk.immodules parser semantics. + */ +#if !defined(HAVE_FLOCKFILE) && \ + defined(HAVE__LOCK_FILE) && \ + defined(HAVE__UNLOCK_FILE) && \ + defined(HAVE__GETC_NOLOCK) +#define flockfile(f) _lock_file(f) +#define funlockfile(f) _unlock_file(f) +#define getc_unlocked(f) _getc_nolock(f) +#elif !defined(HAVE_FLOCKFILE) +#define flockfile(f) (void)1 +#define funlockfile(f) (void)1 +#define getc_unlocked(f) getc(f) +#endif + +static gboolean +gtk_im_module_scan_string (const char **pos, + GString *out) +{ + const char *p = *pos, *q = *pos; + char *tmp, *tmp2; + gboolean quoted; + + while (g_ascii_isspace (*p)) + p++; + + if (!*p) + return FALSE; + else if (*p == '"') + { + p++; + quoted = FALSE; + for (q = p; (*q != '"') || quoted; q++) + { + if (!*q) + return FALSE; + + quoted = (*q == '\\') && !quoted; + } + + tmp = g_strndup (p, q - p); + tmp2 = g_strcompress (tmp); + g_string_truncate (out, 0); + g_string_append (out, tmp2); + g_free (tmp); + g_free (tmp2); + } + else + return FALSE; + + q++; + *pos = q; + + return TRUE; +} + +static gboolean +gtk_im_module_skip_space (const char **pos) +{ + const char *p = *pos; + + while (g_ascii_isspace (*p)) + p++; + + *pos = p; + + return !(*p == '\0'); +} + +static gint +gtk_im_module_read_line (FILE *stream, + GString *str) +{ + gboolean quoted = FALSE; + gboolean comment = FALSE; + int n_read = 0; + int lines = 1; + + flockfile (stream); + + g_string_truncate (str, 0); + + while (1) + { + int c; + + c = getc_unlocked (stream); + + if (c == EOF) + { + if (quoted) + g_string_append_c (str, '\\'); + + goto done; + } + else + n_read++; + + if (quoted) + { + quoted = FALSE; + + switch (c) + { + case '#': + g_string_append_c (str, '#'); + break; + + case '\r': + case '\n': + { + int next_c = getc_unlocked (stream); + + if (!(next_c == EOF || + (c == '\r' && next_c == '\n') || + (c == '\n' && next_c == '\r'))) + ungetc (next_c, stream); + + lines++; + break; + } + + default: + g_string_append_c (str, '\\'); + g_string_append_c (str, c); + } + } + else + { + switch (c) + { + case '#': + comment = TRUE; + break; + + case '\\': + if (!comment) + quoted = TRUE; + break; + + case '\n': + { + int next_c = getc_unlocked (stream); + + if (!(next_c == EOF || + (c == '\r' && next_c == '\n') || + (c == '\n' && next_c == '\r'))) + ungetc (next_c, stream); + + goto done; + } + + default: + if (!comment) + g_string_append_c (str, c); + } + } + } + +done: + funlockfile (stream); + + return (n_read > 0) ? lines : 0; +} + static void gtk_im_module_initialize (void) { @@ -392,13 +562,13 @@ gtk_im_module_initialize (void) return; } - while (!have_error && pango_read_line (file, line_buf)) + while (!have_error && gtk_im_module_read_line (file, line_buf)) { const char *p; p = line_buf->str; - if (!pango_skip_space (&p)) + if (!gtk_im_module_skip_space (&p)) { /* Blank line marking the end of a module */ @@ -418,8 +588,8 @@ gtk_im_module_initialize (void) */ module = g_object_new (GTK_TYPE_IM_MODULE, NULL); - if (!pango_scan_string (&p, tmp_buf) || - pango_skip_space (&p)) + if (!gtk_im_module_scan_string (&p, tmp_buf) || + gtk_im_module_skip_space (&p)) { g_warning ("Error parsing context info in '%s'\n %s", filename, line_buf->str); @@ -438,30 +608,30 @@ gtk_im_module_initialize (void) /* Read information about a context type */ - if (!pango_scan_string (&p, tmp_buf)) + if (!gtk_im_module_scan_string (&p, tmp_buf)) goto context_error; info->context_id = g_strdup (tmp_buf->str); - if (!pango_scan_string (&p, tmp_buf)) + if (!gtk_im_module_scan_string (&p, tmp_buf)) goto context_error; info->context_name = g_strdup (tmp_buf->str); - if (!pango_scan_string (&p, tmp_buf)) + if (!gtk_im_module_scan_string (&p, tmp_buf)) goto context_error; info->domain = g_strdup (tmp_buf->str); - if (!pango_scan_string (&p, tmp_buf)) + if (!gtk_im_module_scan_string (&p, tmp_buf)) goto context_error; info->domain_dirname = g_strdup (tmp_buf->str); #ifdef G_OS_WIN32 correct_localedir_prefix ((char **) &info->domain_dirname); #endif - if (!pango_scan_string (&p, tmp_buf)) + if (!gtk_im_module_scan_string (&p, tmp_buf)) goto context_error; info->default_locales = g_strdup (tmp_buf->str); - if (pango_skip_space (&p)) + if (gtk_im_module_skip_space (&p)) goto context_error; infos = g_slist_prepend (infos, info); @@ -662,7 +832,7 @@ lookup_immodule (gchar **immodules_list) gboolean found; gchar *context_id; found = g_hash_table_lookup_extended (contexts_hash, *immodules_list, - (gpointer *) &context_id, NULL); + (gpointer *) &context_id, NULL); if (found) return context_id; } @@ -721,7 +891,7 @@ _gtk_im_module_get_default_context_id (GdkWindow *client_window) g_strfreev(immodules); g_free (tmp); - if (context_id) + if (context_id) return context_id; } } diff --git a/gtk/gtkmodules.c b/gtk/gtkmodules.c index da5568ffa..061873bdf 100644 --- a/gtk/gtkmodules.c +++ b/gtk/gtkmodules.c @@ -51,6 +51,8 @@ static gboolean default_display_opened = FALSE; static gint gtk_argc = 0; static gchar **gtk_argv = NULL; +static gint module_path_env_dirs = 0; + static gchar ** split_file_list (const gchar *str) { @@ -131,6 +133,7 @@ get_module_path (void) gchar *home_gtk_dir = NULL; gchar *module_path; gchar *default_dir; + gchar *local_dir = NULL; static gchar **result = NULL; if (result) @@ -146,25 +149,67 @@ get_module_path (void) if (exe_prefix) default_dir = g_build_filename (exe_prefix, "lib", "gtk-2.0", NULL); else - default_dir = g_build_filename (GTK_LIBDIR, "gtk-2.0", NULL); + { + default_dir = g_build_filename (GTK_LIBDIR, "gtk-2.0", NULL); - if (module_path_env && home_gtk_dir) +#ifndef G_OS_WIN32 + if (g_str_has_prefix (GTK_LIBDIR, "/usr/lib")) + { + const gchar *lib_subdir = GTK_LIBDIR + strlen ("/usr/"); + + if (lib_subdir[0] != '\0') + local_dir = g_build_filename ("/usr/local", lib_subdir, "gtk-2.0", NULL); + } +#endif + } + + if (module_path_env && home_gtk_dir && local_dir) module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - module_path_env, home_gtk_dir, default_dir, NULL); + module_path_env, home_gtk_dir, local_dir, default_dir, NULL); + else if (module_path_env && home_gtk_dir) + module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, + module_path_env, home_gtk_dir, default_dir, NULL); + else if (module_path_env && local_dir) + module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, + module_path_env, local_dir, default_dir, NULL); else if (module_path_env) module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - module_path_env, default_dir, NULL); + module_path_env, default_dir, NULL); + else if (home_gtk_dir && local_dir) + module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, + home_gtk_dir, local_dir, default_dir, NULL); else if (home_gtk_dir) module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - home_gtk_dir, default_dir, NULL); + home_gtk_dir, default_dir, NULL); + else if (local_dir) + module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, + local_dir, default_dir, NULL); else module_path = g_build_path (G_SEARCHPATH_SEPARATOR_S, - default_dir, NULL); + default_dir, NULL); g_free (home_gtk_dir); + g_free (local_dir); g_free (default_dir); result = split_file_list (module_path); + + if (module_path_env) + { + gchar **env_paths; + gchar **path; + + env_paths = split_file_list (module_path_env); + + module_path_env_dirs = 0; + for (path = env_paths; *path; path++) + module_path_env_dirs++; + + g_strfreev (env_paths); + } + else + module_path_env_dirs = 0; + g_free (module_path); return result; @@ -189,12 +234,15 @@ _gtk_get_module_path (const gchar *type) for (path = paths; *path; path++) count++; - result = g_new (gchar *, count * 5 + 1); + result = g_new (gchar *, count * 4 + module_path_env_dirs + 1); count = 0; - for (path = get_module_path (); *path; path++) + for (path = paths; *path; path++) { gint use_version, use_host; + gboolean path_from_env; + + path_from_env = (path - paths) < module_path_env_dirs; for (use_version = TRUE; use_version >= FALSE; use_version--) for (use_host = TRUE; use_host >= FALSE; use_host--) @@ -219,10 +267,11 @@ _gtk_get_module_path (const gchar *type) * live directly in .../.libs directories, for example print backends, * theme engines or input modules. * - * Keep this limited to absolute paths so the removed fallback that could - * load modules from the current working directory is not reintroduced. + * Keep this limited to absolute GTK_PATH entries so the removed fallback + * that could load modules from the current working directory is not + * reintroduced for built-in default paths. */ - if (g_path_is_absolute (*path)) + if (path_from_env && g_path_is_absolute (*path)) result[count++] = g_strdup (*path); } diff --git a/gtk/gtkprintbackend.c b/gtk/gtkprintbackend.c index 66f5b41a9..669e1c1e2 100644 --- a/gtk/gtkprintbackend.c +++ b/gtk/gtkprintbackend.c @@ -349,6 +349,9 @@ gtk_print_backend_load_modules (void) G_DEFINE_TYPE_WITH_PRIVATE (GtkPrintBackend, gtk_print_backend, G_TYPE_OBJECT) +#define GTK_PRINT_BACKEND_CAN_STORE_AUTH_INFO_KEY "gtk-print-backend-can-store-auth-info" +#define GTK_PRINT_BACKEND_STORE_AUTH_INFO_KEY "gtk-print-backend-store-auth-info" + static void fallback_printer_request_details (GtkPrinter *printer); static gboolean fallback_printer_mark_conflicts (GtkPrinter *printer, GtkPrinterOptionSet *options); @@ -690,6 +693,17 @@ store_entry (GtkEntry *entry, *data = g_strdup (gtk_entry_get_text (entry)); } +static void +store_auth_info_toggled (GtkToggleButton *button, + gpointer user_data) +{ + GtkPrintBackend *backend = GTK_PRINT_BACKEND (user_data); + + g_object_set_data (G_OBJECT (backend), + GTK_PRINT_BACKEND_STORE_AUTH_INFO_KEY, + GINT_TO_POINTER (gtk_toggle_button_get_active (button))); +} + static void password_dialog_response (GtkWidget *dialog, gint response_id, @@ -729,7 +743,7 @@ request_password (GtkPrintBackend *backend, const gchar *prompt) { GtkPrintBackendPrivate *priv = backend->priv; - GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry; + GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry, *chkbtn; GtkWidget *focus = NULL; gchar *markup; gint length; @@ -743,6 +757,10 @@ request_password (GtkPrintBackend *backend, length = g_strv_length (ai_required); priv->auth_info = g_new0 (gchar *, length + 1); + g_object_set_data (G_OBJECT (backend), + GTK_PRINT_BACKEND_STORE_AUTH_INFO_KEY, + GINT_TO_POINTER (FALSE)); + dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, @@ -810,6 +828,17 @@ request_password (GtkPrintBackend *backend, } } + if (g_object_get_data (G_OBJECT (backend), + GTK_PRINT_BACKEND_CAN_STORE_AUTH_INFO_KEY)) + { + chkbtn = gtk_check_button_new_with_mnemonic (_("_Remember password")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chkbtn), FALSE); + gtk_box_pack_start (GTK_BOX (vbox), chkbtn, FALSE, FALSE, 6); + g_signal_connect (chkbtn, "toggled", + G_CALLBACK (store_auth_info_toggled), + backend); + } + if (focus != NULL) { gtk_widget_grab_focus (focus); diff --git a/gtk/gtkprintunixdialog.c b/gtk/gtkprintunixdialog.c index da9010b96..af64a5a37 100644 --- a/gtk/gtkprintunixdialog.c +++ b/gtk/gtkprintunixdialog.c @@ -796,7 +796,9 @@ printer_status_cb (GtkPrintBackend *backend, /* When the pause state change then we need to update sensitive property * of GTK_RESPONSE_OK button inside of selected_printer_changed function. */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->printer_treeview)); + priv->internal_printer_change = TRUE; selected_printer_changed (selection, dialog); + priv->internal_printer_change = FALSE; if (gtk_print_backend_printer_list_is_done (backend) && gtk_printer_is_default (printer) && diff --git a/gtk/gtkquartz.c b/gtk/gtkquartz.c index 87ba87745..caf8404f8 100644 --- a/gtk/gtkquartz.c +++ b/gtk/gtkquartz.c @@ -67,7 +67,10 @@ _gtk_quartz_create_image_from_pixbuf (GdkPixbuf *pixbuf) } [nsimage lockFocus]; - context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + if (gdk_quartz_osx_version () < GDK_OSX_YOSEMITE) + context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + else + context = [[NSGraphicsContext currentContext] CGContext]; CGContextDrawImage (context, CGRectMake (0, 0, pixbuf_width, pixbuf_height), image); [nsimage unlockFocus]; diff --git a/gtk/gtkrc.c b/gtk/gtkrc.c index 7bfbea85c..d316a0d90 100644 --- a/gtk/gtkrc.c +++ b/gtk/gtkrc.c @@ -808,6 +808,8 @@ gtk_rc_parse_named (GtkRcContext *context, gchar *path = NULL; const gchar *home_dir; gchar *subpath; + const gchar * const *xdg_data_dirs; + gint i; if (type) subpath = g_strconcat ("gtk-2.0-", type, @@ -829,6 +831,22 @@ gtk_rc_parse_named (GtkRcContext *context, } } + if (!path) + { + xdg_data_dirs = g_get_system_data_dirs (); + for (i = 0; xdg_data_dirs[i]; i++) + { + path = g_build_filename (xdg_data_dirs[i], "themes", name, subpath, NULL); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + break; + else + { + g_free (path); + path = NULL; + } + } + } + if (!path) { gchar *theme_dir = gtk_rc_get_theme_dir (); diff --git a/gtk/gtkrecentchooser.h b/gtk/gtkrecentchooser.h index 77f01aa79..c5d9732f8 100644 --- a/gtk/gtkrecentchooser.h +++ b/gtk/gtkrecentchooser.h @@ -86,12 +86,12 @@ struct _GtkRecentChooserIface * Methods */ gboolean (* set_current_uri) (GtkRecentChooser *chooser, - const gchar *uri, - GError **error); + const gchar *uri, + GError **error); gchar * (* get_current_uri) (GtkRecentChooser *chooser); gboolean (* select_uri) (GtkRecentChooser *chooser, - const gchar *uri, - GError **error); + const gchar *uri, + GError **error); void (* unselect_uri) (GtkRecentChooser *chooser, const gchar *uri); void (* select_all) (GtkRecentChooser *chooser); @@ -99,14 +99,14 @@ struct _GtkRecentChooserIface GList * (* get_items) (GtkRecentChooser *chooser); GtkRecentManager *(* get_recent_manager) (GtkRecentChooser *chooser); void (* add_filter) (GtkRecentChooser *chooser, - GtkRecentFilter *filter); + GtkRecentFilter *filter); void (* remove_filter) (GtkRecentChooser *chooser, - GtkRecentFilter *filter); + GtkRecentFilter *filter); GSList * (* list_filters) (GtkRecentChooser *chooser); void (* set_sort_func) (GtkRecentChooser *chooser, - GtkRecentSortFunc sort_func, - gpointer sort_data, - GDestroyNotify data_destroy); + GtkRecentSortFunc sort_func, + gpointer sort_data, + GDestroyNotify data_destroy); /* * Signals @@ -176,8 +176,8 @@ gchar ** gtk_recent_chooser_get_uris (GtkRecentChooser *chooser, /* * Filters */ -void gtk_recent_chooser_add_filter (GtkRecentChooser *chooser, - GtkRecentFilter *filter); +void gtk_recent_chooser_add_filter (GtkRecentChooser *chooser, + GtkRecentFilter *filter); void gtk_recent_chooser_remove_filter (GtkRecentChooser *chooser, GtkRecentFilter *filter); GSList * gtk_recent_chooser_list_filters (GtkRecentChooser *chooser); diff --git a/gtk/gtktextbtree.c b/gtk/gtktextbtree.c index 2cd097800..ae3d57ea3 100644 --- a/gtk/gtktextbtree.c +++ b/gtk/gtktextbtree.c @@ -2439,8 +2439,7 @@ _gtk_text_btree_get_text (const GtkTextIter *start_orig, copy_segment (retval, include_hidden, include_nonchars, &iter, &end); - str = retval->str; - g_string_free (retval, FALSE); + str = g_string_free (retval, FALSE); return str; } diff --git a/gtk/gtktooltips.h b/gtk/gtktooltips.h index eb1e0a0be..ee2cc78bd 100644 --- a/gtk/gtktooltips.h +++ b/gtk/gtktooltips.h @@ -74,7 +74,10 @@ struct _GtkTooltips guint have_grab : 1; guint use_sticky_delay : 1; gint timer_tag; - GTimeVal last_popdown; + struct { + glong tv_sec; + glong tv_usec; + } last_popdown; }; struct _GtkTooltipsClass diff --git a/gtk/gtktrayicon.h b/gtk/gtktrayicon.h index 9ad1c54f2..ab4eda2f5 100644 --- a/gtk/gtktrayicon.h +++ b/gtk/gtktrayicon.h @@ -46,12 +46,12 @@ struct _GtkTrayIconClass { GtkPlugClass parent_class; - void (*__gtk_reserved1); - void (*__gtk_reserved2); - void (*__gtk_reserved3); - void (*__gtk_reserved4); - void (*__gtk_reserved5); - void (*__gtk_reserved6); + void (*__gtk_reserved1) (void); + void (*__gtk_reserved2) (void); + void (*__gtk_reserved3) (void); + void (*__gtk_reserved4) (void); + void (*__gtk_reserved5) (void); + void (*__gtk_reserved6) (void); }; GType gtk_tray_icon_get_type (void) G_GNUC_CONST; diff --git a/gtk/gtktreeprivate.h b/gtk/gtktreeprivate.h index b97110029..d93a94a6e 100644 --- a/gtk/gtktreeprivate.h +++ b/gtk/gtktreeprivate.h @@ -465,6 +465,10 @@ void _gtk_tree_view_column_get_neighbor_sizes (GtkTreeViewColumn *c gint *right); +G_GNUC_INTERNAL +void _gtk_tree_view_set_fixed_height_mode_unchecked + (GtkTreeView *tree_view); + G_END_DECLS diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index fc4d79780..767da2ff0 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -48,7 +48,11 @@ #define GTK_TREE_VIEW_PRIORITY_VALIDATE (GDK_PRIORITY_REDRAW + 5) #define GTK_TREE_VIEW_PRIORITY_SCROLL_SYNC (GTK_TREE_VIEW_PRIORITY_VALIDATE + 2) -#define GTK_TREE_VIEW_TIME_MS_PER_IDLE 30 +#if defined(__arm__) && defined(__ARM_ARCH) && (__ARM_ARCH == 6) +#define GTK_TREE_VIEW_TIME_MS_PER_IDLE 10 +#else +#define GTK_TREE_VIEW_TIME_MS_PER_IDLE 2 +#endif #define SCROLL_EDGE_SIZE 15 #define EXPANDER_EXTRA_PADDING 4 #define GTK_TREE_VIEW_SEARCH_DIALOG_TIMEOUT 5000 @@ -7732,6 +7736,25 @@ column_sizing_notify (GObject *object, g_object_set (data, "fixed-height-mode", FALSE, NULL); } +/* Internal fixed-height mode for width-independent rows. */ +G_GNUC_INTERNAL +void +_gtk_tree_view_set_fixed_height_mode_unchecked (GtkTreeView *tree_view) +{ + g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); + + if (tree_view->priv->fixed_height_mode) + return; + + tree_view->priv->fixed_height_mode = TRUE; + tree_view->priv->fixed_height = -1; + + if (tree_view->priv->tree) + initialize_fixed_height_mode (tree_view); + + g_object_notify (G_OBJECT (tree_view), "fixed-height-mode"); +} + /** * gtk_tree_view_set_fixed_height_mode: * @tree_view: a #GtkTreeView @@ -8927,6 +8950,16 @@ gtk_tree_view_clamp_node_visible (GtkTreeView *tree_view, path = _gtk_tree_view_find_path (tree_view, tree, node); if (path) { + /* The first root row at the top cannot require scrolling. */ + if (tree_view->priv->vadjustment->value <= + tree_view->priv->vadjustment->lower && + gtk_tree_path_get_depth (path) == 1 && + gtk_tree_path_get_indices (path)[0] == 0) + { + gtk_tree_path_free (path); + return; + } + /* We process updates because we want to clear old selected items when we scroll. * if this is removed, we get a "selection streak" at the bottom. */ gdk_window_process_updates (tree_view->priv->bin_window, TRUE); diff --git a/gtk/makegtkalias.pl b/gtk/makegtkalias.pl index 3f956b05e..67ec7c973 100755 --- a/gtk/makegtkalias.pl +++ b/gtk/makegtkalias.pl @@ -14,7 +14,7 @@ print < -#ifdef G_HAVE_GNUC_VISIBILITY +#if defined(G_HAVE_GNUC_VISIBILITY) && !defined(__APPLE__) EOF diff --git a/gtk/queryimmodules.c b/gtk/queryimmodules.c index d20637e10..e118b9c19 100644 --- a/gtk/queryimmodules.c +++ b/gtk/queryimmodules.c @@ -159,9 +159,6 @@ query_module (const char *dir, const char *name, GString *contents) { void (*list) (const GtkIMContextInfo ***contexts, guint *n_contexts); - G_GNUC_UNUSED void (*init) (GTypeModule *type_module); - G_GNUC_UNUSED void (*exit) (void); - G_GNUC_UNUSED GtkIMContext *(*create) (const gchar *context_id); gpointer list_ptr; gpointer init_ptr; @@ -196,9 +193,6 @@ query_module (const char *dir, const char *name, GString *contents) int i; list = list_ptr; - init = init_ptr; - exit = exit_ptr; - create = create_ptr; print_escaped (contents, path); g_string_append_c (contents, '\n'); diff --git a/gtk/tests/builder.c b/gtk/tests/builder.c index ebba220e9..121012704 100644 --- a/gtk/tests/builder.c +++ b/gtk/tests/builder.c @@ -132,7 +132,7 @@ static int object = 0; static int object_after = 0; void /* exported for GtkBuilder */ -signal_normal (GtkWindow *window, GParamSpec spec) +signal_normal (GtkWindow *window, GParamSpec *spec) { g_assert (GTK_IS_WINDOW (window)); g_assert (normal == 0); @@ -142,7 +142,7 @@ signal_normal (GtkWindow *window, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_after (GtkWindow *window, GParamSpec spec) +signal_after (GtkWindow *window, GParamSpec *spec) { g_assert (GTK_IS_WINDOW (window)); g_assert (normal == 1); @@ -152,7 +152,7 @@ signal_after (GtkWindow *window, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_object (GtkButton *button, GParamSpec spec) +signal_object (GtkButton *button, GParamSpec *spec) { g_assert (GTK_IS_BUTTON (button)); g_assert (object == 0); @@ -162,7 +162,7 @@ signal_object (GtkButton *button, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_object_after (GtkButton *button, GParamSpec spec) +signal_object_after (GtkButton *button, GParamSpec *spec) { g_assert (GTK_IS_BUTTON (button)); g_assert (object == 1); @@ -172,28 +172,28 @@ signal_object_after (GtkButton *button, GParamSpec spec) } void /* exported for GtkBuilder */ -signal_first (GtkButton *button, GParamSpec spec) +signal_first (GtkButton *button, GParamSpec *spec) { g_assert (normal == 0); normal = 10; } void /* exported for GtkBuilder */ -signal_second (GtkButton *button, GParamSpec spec) +signal_second (GtkButton *button, GParamSpec *spec) { g_assert (normal == 10); normal = 20; } void /* exported for GtkBuilder */ -signal_extra (GtkButton *button, GParamSpec spec) +signal_extra (GtkButton *button, GParamSpec *spec) { g_assert (normal == 20); normal = 30; } void /* exported for GtkBuilder */ -signal_extra2 (GtkButton *button, GParamSpec spec) +signal_extra2 (GtkButton *button, GParamSpec *spec) { g_assert (normal == 30); normal = 40; diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c index 4a3158e12..b46d97c04 100644 --- a/gtk/updateiconcache.c +++ b/gtk/updateiconcache.c @@ -608,6 +608,16 @@ replace_backslashes_with_slashes (gchar *path) path[i] = '/'; } +static gint +compare_string_pointers (gconstpointer a, + gconstpointer b) +{ + const gchar * const *str_a = a; + const gchar * const *str_b = b; + + return strcmp (*str_a, *str_b); +} + static GList * scan_directory (const gchar *base_path, const gchar *subdir, @@ -617,10 +627,12 @@ scan_directory (const gchar *base_path, { GHashTable *dir_hash; GDir *dir; + GPtrArray *names; const gchar *name; gchar *dir_path; gboolean dir_added = FALSE; guint dir_index = 0xffff; + guint i; dir_path = g_build_path ("/", base_path, subdir, NULL); @@ -628,12 +640,25 @@ scan_directory (const gchar *base_path, dir = g_dir_open (dir_path, 0, NULL); if (!dir) - return directories; + { + g_free (dir_path); + return directories; + } + + names = g_ptr_array_new_with_free_func (g_free); + + while ((name = g_dir_read_name (dir))) + g_ptr_array_add (names, g_strdup (name)); + + g_dir_close (dir); + + g_ptr_array_sort (names, compare_string_pointers); dir_hash = g_hash_table_new (g_str_hash, g_str_equal); - while ((name = g_dir_read_name (dir))) + for (i = 0; i < names->len; i++) { + name = g_ptr_array_index (names, i); gchar *path; gboolean retval; int flags = 0; @@ -712,7 +737,8 @@ scan_directory (const gchar *base_path, g_free (path); } - g_dir_close (dir); + g_ptr_array_free (names, TRUE); + g_free (dir_path); /* Move dir into the big file hash */ g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files); diff --git a/modules/other/gail/gailbutton.c b/modules/other/gail/gailbutton.c index 6ecf9cd74..495efce1b 100644 --- a/modules/other/gail/gailbutton.c +++ b/modules/other/gail/gailbutton.c @@ -1021,7 +1021,7 @@ gail_button_get_image_position (AtkImage *image, if (button_image != NULL) { obj = gtk_widget_get_accessible (GTK_WIDGET (button_image)); - atk_component_get_position (ATK_COMPONENT (obj), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (obj), x, y, NULL, NULL, coord_type); } else { diff --git a/modules/other/gail/gailimage.c b/modules/other/gail/gailimage.c index 0a33cd1ec..968b1d464 100644 --- a/modules/other/gail/gailimage.c +++ b/modules/other/gail/gailimage.c @@ -179,7 +179,7 @@ gail_image_get_image_position (AtkImage *image, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/gailimagecell.c b/modules/other/gail/gailimagecell.c index efcded036..5860b723f 100644 --- a/modules/other/gail/gailimagecell.c +++ b/modules/other/gail/gailimagecell.c @@ -150,7 +150,7 @@ gail_image_cell_get_image_position (AtkImage *image, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (image), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (image), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/gailnotebookpage.c b/modules/other/gail/gailnotebookpage.c index 444ddac0e..cd4e18d7d 100644 --- a/modules/other/gail/gailnotebookpage.c +++ b/modules/other/gail/gailnotebookpage.c @@ -467,7 +467,7 @@ gail_notebook_page_get_extents (AtkComponent *component, child = atk_object_ref_accessible_child (ATK_OBJECT (component), 0); gail_return_if_fail (child); - atk_component_get_position (ATK_COMPONENT (child), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (child), x, y, NULL, NULL, coord_type); g_object_unref (child); } else diff --git a/modules/other/gail/gailpaned.c b/modules/other/gail/gailpaned.c index 5ac2d2340..4b106e205 100644 --- a/modules/other/gail/gailpaned.c +++ b/modules/other/gail/gailpaned.c @@ -43,6 +43,14 @@ static void gail_paned_get_minimum_value (AtkValue *obj, GValue *value); static gboolean gail_paned_set_current_value (AtkValue *obj, const GValue *value); +#if ATK_CHECK_VERSION (2, 12, 0) +static void gail_paned_get_value_and_text (AtkValue *obj, + gdouble *value, + gchar **text); +static AtkRange *gail_paned_get_range (AtkValue *obj); +static void gail_paned_set_value (AtkValue *obj, + gdouble new_value); +#endif G_DEFINE_TYPE_WITH_CODE (GailPaned, gail_paned, GAIL_TYPE_CONTAINER, G_IMPLEMENT_INTERFACE (ATK_TYPE_VALUE, atk_value_interface_init)) @@ -112,6 +120,11 @@ atk_value_interface_init (AtkValueIface *iface) iface->get_maximum_value = gail_paned_get_maximum_value; iface->get_minimum_value = gail_paned_get_minimum_value; iface->set_current_value = gail_paned_set_current_value; +#if ATK_CHECK_VERSION (2, 12, 0) + iface->get_value_and_text = gail_paned_get_value_and_text; + iface->get_range = gail_paned_get_range; + iface->set_value = gail_paned_set_value; +#endif } static void @@ -197,3 +210,58 @@ gail_paned_set_current_value (AtkValue *obj, else return FALSE; } + +#if ATK_CHECK_VERSION (2, 12, 0) +static void +gail_paned_get_value_and_text (AtkValue *obj, + gdouble *value, + gchar **text) +{ + GtkWidget *widget; + + if (text) + *text = NULL; + + if (!value) + return; + + widget = GTK_ACCESSIBLE (obj)->widget; + if (widget == NULL) + { + *value = 0.0; + return; + } + + *value = gtk_paned_get_position (GTK_PANED (widget)); +} + +static AtkRange * +gail_paned_get_range (AtkValue *obj) +{ + GtkWidget *widget; + GtkPaned *paned; + + widget = GTK_ACCESSIBLE (obj)->widget; + if (widget == NULL) + return NULL; + + paned = GTK_PANED (widget); + + return atk_range_new (paned->min_position, paned->max_position, NULL); +} + +static void +gail_paned_set_value (AtkValue *obj, + gdouble value) +{ + GtkWidget *widget; + gint new_value; + + widget = GTK_ACCESSIBLE (obj)->widget; + if (widget == NULL) + return; + + new_value = (gint) value; + gtk_paned_set_position (GTK_PANED (widget), new_value); +} +#endif diff --git a/modules/other/gail/gailpixmap.c b/modules/other/gail/gailpixmap.c index b8ab4b3da..4f44f93be 100644 --- a/modules/other/gail/gailpixmap.c +++ b/modules/other/gail/gailpixmap.c @@ -103,7 +103,7 @@ gail_pixmap_get_image_position (AtkImage *obj, gint *y, AtkCoordType coord_type) { - atk_component_get_position (ATK_COMPONENT (obj), x, y, coord_type); + atk_component_get_extents (ATK_COMPONENT (obj), x, y, NULL, NULL, coord_type); } static void diff --git a/modules/other/gail/gailprogressbar.c b/modules/other/gail/gailprogressbar.c index d70c139be..99f32dc65 100644 --- a/modules/other/gail/gailprogressbar.c +++ b/modules/other/gail/gailprogressbar.c @@ -125,7 +125,17 @@ gail_progress_bar_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (progress_bar->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (progress_bar->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->value); + } } static void @@ -143,7 +153,17 @@ gail_progress_bar_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (progress_bar->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (progress_bar->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->upper); + } } static void @@ -161,7 +181,17 @@ gail_progress_bar_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (progress_bar->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (progress_bar->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->lower); + } } static void diff --git a/modules/other/gail/gailrange.c b/modules/other/gail/gailrange.c index 9a91bb333..56ab04763 100644 --- a/modules/other/gail/gailrange.c +++ b/modules/other/gail/gailrange.c @@ -180,7 +180,17 @@ gail_range_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (range->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (range->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->value); + } } static void @@ -201,7 +211,17 @@ gail_range_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (range->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (range->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->upper); + } gtk_range = GTK_RANGE (gtk_accessible_get_widget (GTK_ACCESSIBLE (range))); g_return_if_fail (gtk_range); @@ -231,7 +251,17 @@ gail_range_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (range->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (range->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->lower); + } } static void @@ -248,7 +278,31 @@ gail_range_get_minimum_increment (AtkValue *obj, GValue *value) */ return; - atk_value_get_minimum_increment (ATK_VALUE (range->adjustment), value); + { + GtkAdjustment *adjustment; + gdouble minimum_increment; + + adjustment = GAIL_ADJUSTMENT (range->adjustment)->adjustment; + if (adjustment == NULL) + return; + + if (adjustment->step_increment != 0 && + adjustment->page_increment != 0) + { + if (ABS (adjustment->step_increment) < ABS (adjustment->page_increment)) + minimum_increment = adjustment->step_increment; + else + minimum_increment = adjustment->page_increment; + } + else if (adjustment->step_increment != 0) + minimum_increment = adjustment->step_increment; + else + minimum_increment = adjustment->page_increment; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, minimum_increment); + } } static gboolean gail_range_set_current_value (AtkValue *obj, diff --git a/modules/other/gail/gailspinbutton.c b/modules/other/gail/gailspinbutton.c index 709a413c0..b209cc403 100644 --- a/modules/other/gail/gailspinbutton.c +++ b/modules/other/gail/gailspinbutton.c @@ -127,7 +127,17 @@ gail_spin_button_get_current_value (AtkValue *obj, */ return; - atk_value_get_current_value (ATK_VALUE (spin_button->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (spin_button->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->value); + } } static void @@ -145,7 +155,17 @@ gail_spin_button_get_maximum_value (AtkValue *obj, */ return; - atk_value_get_maximum_value (ATK_VALUE (spin_button->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (spin_button->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->upper); + } } static void @@ -163,7 +183,17 @@ gail_spin_button_get_minimum_value (AtkValue *obj, */ return; - atk_value_get_minimum_value (ATK_VALUE (spin_button->adjustment), value); + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (spin_button->adjustment)->adjustment; + if (adjustment == NULL) + return; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, adjustment->lower); + } } static void @@ -180,7 +210,31 @@ gail_spin_button_get_minimum_increment (AtkValue *obj, GValue *value) */ return; - atk_value_get_minimum_increment (ATK_VALUE (spin_button->adjustment), value); + { + GtkAdjustment *adjustment; + gdouble minimum_increment; + + adjustment = GAIL_ADJUSTMENT (spin_button->adjustment)->adjustment; + if (adjustment == NULL) + return; + + if (adjustment->step_increment != 0 && + adjustment->page_increment != 0) + { + if (ABS (adjustment->step_increment) < ABS (adjustment->page_increment)) + minimum_increment = adjustment->step_increment; + else + minimum_increment = adjustment->page_increment; + } + else if (adjustment->step_increment != 0) + minimum_increment = adjustment->step_increment; + else + minimum_increment = adjustment->page_increment; + + memset (value, 0, sizeof (GValue)); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, minimum_increment); + } } static gboolean @@ -198,7 +252,19 @@ gail_spin_button_set_current_value (AtkValue *obj, */ return FALSE; - return atk_value_set_current_value (ATK_VALUE (spin_button->adjustment), value); + if (G_VALUE_HOLDS_DOUBLE (value)) + { + GtkAdjustment *adjustment; + + adjustment = GAIL_ADJUSTMENT (spin_button->adjustment)->adjustment; + if (adjustment == NULL) + return FALSE; + + gtk_adjustment_set_value (adjustment, g_value_get_double (value)); + return TRUE; + } + + return FALSE; } static void diff --git a/modules/other/gail/libgail-util/gailmisc.c b/modules/other/gail/libgail-util/gailmisc.c index 9dc143d07..8213c716a 100644 --- a/modules/other/gail/libgail-util/gailmisc.c +++ b/modules/other/gail/libgail-util/gailmisc.c @@ -251,7 +251,7 @@ gail_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set, if ((pango_string = (PangoAttrString*) pango_attr_iterator_get (iter, PANGO_ATTR_FAMILY)) != NULL) { - value = g_strdup_printf("%s", pango_string->value); + value = g_strdup (pango_string->value); attrib_set = gail_misc_add_attribute (attrib_set, ATK_TEXT_ATTR_FAMILY_NAME, value); diff --git a/modules/other/gail/tests/ferret.c b/modules/other/gail/tests/ferret.c index 711852af0..e4662cd9b 100644 --- a/modules/other/gail/tests/ferret.c +++ b/modules/other/gail/tests/ferret.c @@ -1512,9 +1512,10 @@ _button_watcher (GSignalInvocationHint *ihint, event = (GdkEventButton *) gp; aobject = gtk_widget_get_accessible (widget); aobject_x = aobject_y = 0; - atk_component_get_position (ATK_COMPONENT (aobject), - &aobject_x, &aobject_y, - ATK_XY_WINDOW); + atk_component_get_extents (ATK_COMPONENT (aobject), + &aobject_x, &aobject_y, + NULL, NULL, + ATK_XY_WINDOW); x = aobject_x + (gint) event->x; y = aobject_y + (gint) event->y; child = atk_component_ref_accessible_at_point (ATK_COMPONENT (aobject), diff --git a/modules/other/gail/tests/testcomponent.c b/modules/other/gail/tests/testcomponent.c index a28eccebd..df2d2c925 100644 --- a/modules/other/gail/tests/testcomponent.c +++ b/modules/other/gail/tests/testcomponent.c @@ -16,11 +16,11 @@ static void _check_position (AtkObject *obj) return; atk_component_get_extents (ATK_COMPONENT(obj), &x, &y, &width, &height, ATK_XY_SCREEN); - atk_component_get_position (ATK_COMPONENT(obj), &x1, &y1, ATK_XY_SCREEN ); + atk_component_get_extents (ATK_COMPONENT(obj), &x1, &y1, NULL, NULL, ATK_XY_SCREEN); atk_component_get_size (ATK_COMPONENT(obj), &width1, &height1); if ((x1 != x) || (y1 != y)) { - g_print ("atk_component_get_extents and atk_get_position give different" + g_print ("Repeated atk_component_get_extents calls give different" " values: %d,%d %d,%d\n", x, y, x1, y1); } if ((width1 != width) || (height1 != height)) @@ -29,7 +29,7 @@ static void _check_position (AtkObject *obj) " values: %d,%d %d,%d\n", width, height, width1, height1); } - atk_component_get_position (ATK_COMPONENT(obj), &x1, &y1, ATK_XY_SCREEN); + atk_component_get_extents (ATK_COMPONENT(obj), &x1, &y1, NULL, NULL, ATK_XY_SCREEN); g_print ("Object Type: %s\n", g_type_name (G_OBJECT_TYPE (obj))); g_print ("Object at %d, %d on screen\n", x1, y1); g_print ("Object at %d, %d, size: %d, %d\n", x, y, width, height); diff --git a/modules/other/gail/tests/testpaned.c b/modules/other/gail/tests/testpaned.c index 300031955..781750a86 100644 --- a/modules/other/gail/tests/testpaned.c +++ b/modules/other/gail/tests/testpaned.c @@ -12,6 +12,103 @@ static void _property_change_handler (AtkObject *obj, #define NUM_VALID_ROLES 1 static gint last_position; +static gint +test_value_get_current_int (AtkValue *obj) +{ +#if ATK_CHECK_VERSION (2, 12, 0) + gdouble value = 0.0; + + atk_value_get_value_and_text (obj, &value, NULL); + + return (gint) value; +#else + GValue value = G_VALUE_INIT; + gint result; + + atk_value_get_current_value (obj, &value); + g_return_val_if_fail (G_VALUE_HOLDS_INT (&value), 0); + + result = g_value_get_int (&value); + g_value_unset (&value); + + return result; +#endif +} + +static gint +test_value_get_minimum_int (AtkValue *obj) +{ +#if ATK_CHECK_VERSION (2, 12, 0) + AtkRange *range; + gint value = 0; + + range = atk_value_get_range (obj); + if (range) + { + value = (gint) atk_range_get_lower_limit (range); + atk_range_free (range); + } + + return value; +#else + GValue value = G_VALUE_INIT; + gint result; + + atk_value_get_minimum_value (obj, &value); + g_return_val_if_fail (G_VALUE_HOLDS_INT (&value), 0); + + result = g_value_get_int (&value); + g_value_unset (&value); + + return result; +#endif +} + +static gint +test_value_get_maximum_int (AtkValue *obj) +{ +#if ATK_CHECK_VERSION (2, 12, 0) + AtkRange *range; + gint value = 0; + + range = atk_value_get_range (obj); + if (range) + { + value = (gint) atk_range_get_upper_limit (range); + atk_range_free (range); + } + + return value; +#else + GValue value = G_VALUE_INIT; + gint result; + + atk_value_get_maximum_value (obj, &value); + g_return_val_if_fail (G_VALUE_HOLDS_INT (&value), 0); + + result = g_value_get_int (&value); + g_value_unset (&value); + + return result; +#endif +} + +static void +test_value_set_current_int (AtkValue *obj, + gint value) +{ +#if ATK_CHECK_VERSION (2, 12, 0) + atk_value_set_value (obj, value); +#else + GValue gvalue = G_VALUE_INIT; + + g_value_init (&gvalue, G_TYPE_INT); + g_value_set_int (&gvalue, value); + atk_value_set_current_value (obj, &gvalue); + g_value_unset (&gvalue); +#endif +} + static void _property_change_handler (AtkObject *obj, AtkPropertyValues *values) { @@ -26,24 +123,17 @@ static void _property_change_handler (AtkObject *obj, values->property_name ? values->property_name: "NULL"); if (strcmp (values->property_name, "accessible-value") == 0) { - GValue *value, val; int position; - value = &val; - memset (value, 0, sizeof (GValue)); - atk_value_get_current_value (ATK_VALUE (obj), value); - g_return_if_fail (G_VALUE_HOLDS_INT (value)); - position = g_value_get_int (value); + position = test_value_get_current_int (ATK_VALUE (obj)); g_print ("Position is %d previous position was %d\n", position, last_position); last_position = position; - atk_value_get_minimum_value (ATK_VALUE (obj), value); - g_return_if_fail (G_VALUE_HOLDS_INT (value)); - position = g_value_get_int (value); + + position = test_value_get_minimum_int (ATK_VALUE (obj)); g_print ("Minimum Value is %d\n", position); - atk_value_get_maximum_value (ATK_VALUE (obj), value); - g_return_if_fail (G_VALUE_HOLDS_INT (value)); - position = g_value_get_int (value); + + position = test_value_get_maximum_int (ATK_VALUE (obj)); g_print ("Maximum Value is %d\n", position); } } @@ -56,19 +146,13 @@ static gint _test_paned (gpointer data) if (role == ATK_ROLE_SPLIT_PANE) { - GValue *value, val; int position; - value = &val; - memset (value, 0, sizeof (GValue)); - atk_value_get_current_value (ATK_VALUE (obj), value); - g_return_val_if_fail (G_VALUE_HOLDS_INT (value), FALSE); - position = g_value_get_int (value); + position = test_value_get_current_int (ATK_VALUE (obj)); g_print ("Position is : %d\n", position); last_position = position; position *= 2; - g_value_set_int (value, position); - atk_value_set_current_value (ATK_VALUE (obj), value); + test_value_set_current_int (ATK_VALUE (obj), position); times++; } if (times < 4) diff --git a/modules/printbackends/cups/Makefile.am b/modules/printbackends/cups/Makefile.am index 5bc26cde6..369d8446c 100644 --- a/modules/printbackends/cups/Makefile.am +++ b/modules/printbackends/cups/Makefile.am @@ -27,12 +27,14 @@ backend_LTLIBRARIES = libprintbackend-cups.la libprintbackend_cups_la_SOURCES = \ gtkprintbackendcups.c \ gtkprintercups.c \ - gtkcupsutils.c + gtkcupsutils.c \ + gtkcupssecretsutils.c noinst_HEADERS = \ gtkprintbackendcups.h \ gtkprintercups.h \ - gtkcupsutils.h + gtkcupsutils.h \ + gtkcupssecretsutils.h libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined) libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS) diff --git a/modules/printbackends/cups/gtkcupssecretsutils.c b/modules/printbackends/cups/gtkcupssecretsutils.c new file mode 100644 index 000000000..3b1a95db0 --- /dev/null +++ b/modules/printbackends/cups/gtkcupssecretsutils.c @@ -0,0 +1,1048 @@ +/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords + * Copyright (C) 2014, Intevation GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include +#include +#include + +#include + +#include "gtkcupssecretsutils.h" + +#define SECRETS_BUS "org.freedesktop.secrets" +#define SECRETS_IFACE(interface) "org.freedesktop.Secret."interface +#define SECRETS_PATH "/org/freedesktop/secrets" +#define SECRETS_TIMEOUT 5000 + +typedef enum +{ + SECRETS_SERVICE_ACTION_QUERY, + SECRETS_SERVICE_ACTION_STORE +} SecretsServiceAction; + +typedef struct +{ + GDBusConnection *dbus_connection; + SecretsServiceAction action; + gchar **auth_info, + **auth_info_labels, + **auth_info_required, + *printer_uri, + *session_path, + *collection_path; + GDBusProxy *item_proxy; + guint prompt_subscription; +} SecretsServiceData; + +/** + * create_attributes: + * @printer_uri: URI for the printer + * @additional_labels: Optional labels for additional attributes + * @additional_attrs: Optional additional attributes + * + * Creates a GVariant dictionary with key / value pairs that + * can be used to identify a secret item. + * + * Returns: A GVariant dictionary of string pairs or NULL on error. + */ +static GVariant * +create_attributes (const gchar *printer_uri, + gchar **additional_attrs, + gchar **additional_labels) +{ + GVariantBuilder *attr_builder = NULL; + GVariant *ret = NULL; + + if (printer_uri == NULL) + { + GTK_NOTE (PRINTING, + g_print ("create_attributes called with invalid parameters.\n")); + return NULL; + } + + attr_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY); + /* The printer uri is the main identifying part */ + g_variant_builder_add (attr_builder, "{ss}", "uri", printer_uri); + + if (additional_labels != NULL && additional_attrs != NULL) + { + int i; + for (i = 0; additional_labels[i] != NULL; i++) + { + g_variant_builder_add (attr_builder, "{ss}", + additional_labels[i], + additional_attrs[i]); + } + } + + ret = g_variant_builder_end (attr_builder); + g_variant_builder_unref (attr_builder); + + return ret; +} + +static void +get_secret_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GVariant *output, + *attributes; + gchar **auth_info = NULL, + *key = NULL, + *value = NULL; + GVariantIter *iter = NULL; + guint i; + gint pw_field = -1; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + attributes = g_dbus_proxy_get_cached_property (task_data->item_proxy, + "Attributes"); + if (attributes == NULL) + { + GTK_NOTE (PRINTING, g_print ("Failed to lookup attributes.\n")); + g_variant_unref (output); + g_task_return_pointer (task, NULL, NULL); + return; + } + + /* Iterate over the attributes to fill the auth info */ + g_variant_get (attributes, "a{ss}", &iter); + + auth_info = g_new0 (gchar *, + g_strv_length (task_data->auth_info_required) + 1); + + while (g_variant_iter_loop (iter, "{ss}", &key, &value)) + { + /* Match attributes with required auth info */ + for (i = 0; task_data->auth_info_required[i] != NULL; i++) + { + if ((strcmp (key, "user") == 0 || + strcmp (key, "username") == 0) && + strcmp (task_data->auth_info_required[i], + "username") == 0) + { + auth_info[i] = g_strdup (value); + } + else if (strcmp (key, "domain") == 0 && + strcmp (task_data->auth_info_required[i], "domain") == 0) + { + auth_info[i] = g_strdup (value); + } + else if ((strcmp (key, "hostname") == 0 || + strcmp (key, "server") == 0 ) && + strcmp (task_data->auth_info_required[i], "hostname") == 0) + { + auth_info[i] = g_strdup (value); + } + else if (strcmp (task_data->auth_info_required[i], "password") == 0) + { + pw_field = i; + } + } + } + + g_variant_iter_free (iter); + g_variant_unref (attributes); + + if (pw_field == -1) + { + /* should not happen... */ + GTK_NOTE (PRINTING, g_print ("No password required?.\n")); + g_variant_unref (output); + goto fail; + } + else + { + GVariant *secret, + *s_value; + gconstpointer ba_passwd = NULL; + gsize len = 0; + + secret = g_variant_get_child_value (output, 0); + g_variant_unref (output); + if (secret == NULL || g_variant_n_children (secret) != 4) + { + GTK_NOTE (PRINTING, g_print ("Get secret response invalid.\n")); + if (secret != NULL) + g_variant_unref (secret); + goto fail; + } + s_value = g_variant_get_child_value (secret, 2); + ba_passwd = g_variant_get_fixed_array (s_value, + &len, + sizeof (guchar)); + + g_variant_unref (secret); + + if (ba_passwd == NULL) + { + /* No secret or the secret is not a zero terminated value */ + GTK_NOTE (PRINTING, g_print ("Invalid / no secret found.\n")); + g_variant_unref (s_value); + goto fail; + } + + auth_info[pw_field] = g_strndup (ba_passwd, len); + g_variant_unref (s_value); + } + + for (i = 0; task_data->auth_info_required[i] != NULL; i++) + { + if (auth_info[i] == NULL) + { + /* Error out if we did not find everything */ + GTK_NOTE (PRINTING, g_print ("Failed to lookup required attribute: %s.\n", + task_data->auth_info_required[i])); + goto fail; + } + } + + g_task_return_pointer (task, auth_info, NULL); + return; + +fail: + /* Error out */ + GTK_NOTE (PRINTING, g_print ("Failed to lookup secret.\n")); + for (i = 0; i < g_strv_length (task_data->auth_info_required); i++) + { + /* Not all fields of auth_info are neccessarily written so we can not + use strfreev here */ + g_free (auth_info[i]); + } + g_free (auth_info); + g_task_return_pointer (task, NULL, NULL); +} + +static void +create_item_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + GError *error = NULL; + GVariant *output; + gchar *item = NULL; + + task = user_data; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + g_variant_get (output, "(&o&o)", &item, NULL); + if (item != NULL && strlen (item) > 1) + { + GTK_NOTE (PRINTING, g_print ("Successfully stored auth info.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + g_variant_unref (output); +} + +static void +do_store_auth_info (GTask *task) +{ + GVariant *attributes = NULL, + *properties = NULL, + *secret = NULL; + gchar **additional_attrs = NULL, + **additional_labels = NULL, + *password = NULL; + SecretsServiceData *task_data = g_task_get_task_data (task); + guint i, + length, + additional_count = 0; + GVariantBuilder *prop_builder = NULL; + + length = g_strv_length (task_data->auth_info_labels); + + additional_attrs = g_new0 (gchar *, length + 1); + additional_labels = g_new0 (gchar *, length + 1); + /* The labels user and server are chosen to be compatible with + the attributes used by system-config-printer */ + for (i = 0; task_data->auth_info_labels[i] != NULL; i++) + { + if (g_strcmp0 (task_data->auth_info_labels[i], "username") == 0) + { + additional_attrs[additional_count] = task_data->auth_info[i]; + additional_labels[additional_count++] = "user"; + } + else if (g_strcmp0 (task_data->auth_info_labels[i], "hostname") == 0) + { + additional_attrs[additional_count] = task_data->auth_info[i]; + additional_labels[additional_count++] = "server"; + } + else if (g_strcmp0 (task_data->auth_info_labels[i], "password") == 0) + { + password = task_data->auth_info[i]; + } + } + + attributes = create_attributes (task_data->printer_uri, + additional_attrs, + additional_labels); + g_free (additional_labels); + g_free (additional_attrs); + if (attributes == NULL) + { + GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + if (password == NULL) + { + GTK_NOTE (PRINTING, g_print ("No secret to store.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + prop_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY); + + g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Label"), + g_variant_new_string (task_data->printer_uri)); + g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Attributes"), + attributes); + + properties = g_variant_builder_end (prop_builder); + + g_variant_builder_unref (prop_builder); + + secret = g_variant_new ("(oay@ays)", + task_data->session_path, + NULL, + g_variant_new_bytestring (password), + "text/plain"); + + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + task_data->collection_path, + SECRETS_IFACE ("Collection"), + "CreateItem", + g_variant_new ("(@a{sv}@(oayays)b)", + properties, + secret, + TRUE), + G_VARIANT_TYPE ("(oo)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + create_item_cb, + task); +} + +static void +prompt_completed_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GVariant *dismissed; + gboolean is_dismissed = TRUE; + + task = user_data; + task_data = g_task_get_task_data (task); + + g_dbus_connection_signal_unsubscribe (task_data->dbus_connection, + task_data->prompt_subscription); + task_data->prompt_subscription = 0; + + dismissed = g_variant_get_child_value (parameters, 0); + + if (dismissed == NULL) + { + GTK_NOTE (PRINTING, g_print ("Invalid prompt signal.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + g_variant_get (dismissed, "b", &is_dismissed); + g_variant_unref (dismissed); + + if (is_dismissed) + { + GTK_NOTE (PRINTING, g_print ("Collection unlock dismissed.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + /* Prompt successfull, proceed to get or store secret */ + switch (task_data->action) + { + case SECRETS_SERVICE_ACTION_STORE: + do_store_auth_info (task); + break; + + case SECRETS_SERVICE_ACTION_QUERY: + g_dbus_proxy_call (task_data->item_proxy, + "GetSecret", + g_variant_new ("(o)", + task_data->session_path), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + get_secret_cb, + task); + break; + } +} + +static void +prompt_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GVariant *output; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + g_variant_unref (output); + + /* Connect to the prompt's completed signal */ + task_data->prompt_subscription = + g_dbus_connection_signal_subscribe (task_data->dbus_connection, + NULL, + SECRETS_IFACE ("Prompt"), + "Completed", + NULL, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + prompt_completed_cb, + task, + NULL); +} + +static void +unlock_collection_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GVariant *output; + const gchar *prompt_path; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + g_variant_get (output, "(@ao&o)", NULL, &prompt_path); + + if (prompt_path != NULL && strlen (prompt_path) > 1) + { + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + prompt_path, + SECRETS_IFACE ("Prompt"), + "Prompt", + g_variant_new ("(s)", "0"), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + prompt_cb, + task); + } + else + { + switch (task_data->action) + { + case SECRETS_SERVICE_ACTION_STORE: + do_store_auth_info (task); + break; + + case SECRETS_SERVICE_ACTION_QUERY: + /* Prompt successfull proceed to get secret */ + g_dbus_proxy_call (task_data->item_proxy, + "GetSecret", + g_variant_new ("(o)", + task_data->session_path), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + get_secret_cb, + task); + break; + } + } + g_variant_unref (output); +} + +static void +unlock_read_alias_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GVariant *output, + *subresult; + gsize path_len = 0; + const gchar *collection_path; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + subresult = g_variant_get_child_value (output, 0); + g_variant_unref (output); + + if (subresult == NULL) + { + GTK_NOTE (PRINTING, g_print ("Invalid ReadAlias response.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + collection_path = g_variant_get_string (subresult, &path_len); + + const gchar * const to_unlock[] = + { + collection_path, NULL + }; + + task_data->collection_path = g_strdup (collection_path); + + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + SECRETS_PATH, + SECRETS_IFACE ("Service"), + "Unlock", + g_variant_new ("(^ao)", to_unlock), + G_VARIANT_TYPE ("(aoo)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + unlock_collection_cb, + task); + + g_variant_unref (subresult); +} + +static void +item_proxy_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GDBusProxy *item_proxy; + GVariant *locked; + gboolean is_locked; + + task = user_data; + task_data = g_task_get_task_data (task); + + item_proxy = g_dbus_proxy_new_finish (res, + &error); + if (item_proxy == NULL) + { + g_task_return_error (task, error); + return; + } + + task_data->item_proxy = item_proxy; + + locked = g_dbus_proxy_get_cached_property (item_proxy, "Locked"); + + if (locked == NULL) + { + GTK_NOTE (PRINTING, g_print ("Failed to look up \"Locked\" property on item.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + g_variant_get (locked, "b", &is_locked); + g_variant_unref (locked); + + if (is_locked) + { + /* Go down the unlock -> lookup path */ + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + SECRETS_PATH, + SECRETS_IFACE ("Service"), + "ReadAlias", + g_variant_new ("(s)", "default"), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + unlock_read_alias_cb, + task); + return; + } + + /* Unlocked proceed to get or store secret */ + switch (task_data->action) + { + case SECRETS_SERVICE_ACTION_STORE: + do_store_auth_info (task); + break; + + case SECRETS_SERVICE_ACTION_QUERY: + g_dbus_proxy_call (item_proxy, + "GetSecret", + g_variant_new ("(o)", + task_data->session_path), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + get_secret_cb, + task); + break; + } +} + +static void +search_items_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + GVariant *output; + gsize array_cnt, + i; + gboolean found_item = FALSE; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + array_cnt = g_variant_n_children (output); + + for (i = 0; i < array_cnt; i++) + { + GVariant * const item_paths = g_variant_get_child_value (output, i); + const gchar **items = NULL; + + if (item_paths == NULL) + { + GTK_NOTE (PRINTING, + g_print ("SearchItems returned invalid result.\n")); + continue; + } + + items = g_variant_get_objv (item_paths, NULL); + + if (*items == NULL) + { + g_variant_unref (item_paths); + g_free ((gpointer) items); + continue; + } + + /* Access the first found item. */ + found_item = TRUE; + g_dbus_proxy_new (task_data->dbus_connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + SECRETS_BUS, + *items, + SECRETS_IFACE ("Item"), + g_task_get_cancellable (task), + item_proxy_cb, + task); + g_free ((gpointer) items); + g_variant_unref (item_paths); + break; + } + g_variant_unref (output); + + if (!found_item) + { + GTK_NOTE (PRINTING, g_print ("No match found in secrets service.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } +} + +static void +open_session_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + GVariant *output, + *session_variant; + SecretsServiceData *task_data; + GError *error = NULL; + + task = user_data; + task_data = g_task_get_task_data (task); + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + if (output == NULL) + { + g_task_return_error (task, error); + return; + } + + session_variant = g_variant_get_child_value (output, 1); + + if (session_variant == NULL) + { + GTK_NOTE (PRINTING, g_print ("Invalid session path response.\n")); + g_variant_unref (output); + g_task_return_pointer (task, NULL, NULL); + return; + } + + task_data->session_path = g_variant_dup_string (session_variant, NULL); + + if (task_data->session_path == NULL) + { + GTK_NOTE (PRINTING, g_print ("Invalid session path string value.\n")); + g_variant_unref (session_variant); + g_variant_unref (output); + g_task_return_pointer (task, NULL, NULL); + return; + } + + g_variant_unref (session_variant); + g_variant_unref (output); + + switch (task_data->action) + { + case SECRETS_SERVICE_ACTION_QUERY: + { + /* Search for the secret item */ + GVariant *secrets_attrs; + + secrets_attrs = create_attributes (task_data->printer_uri, NULL, NULL); + if (secrets_attrs == NULL) + { + GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n")); + g_task_return_pointer (task, NULL, NULL); + return; + } + + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + SECRETS_PATH, + SECRETS_IFACE ("Service"), + "SearchItems", + g_variant_new ("(@a{ss})", secrets_attrs), + G_VARIANT_TYPE ("(aoao)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + search_items_cb, + task); + break; + } + + case SECRETS_SERVICE_ACTION_STORE: + { + /* Look up / unlock the default collection for storing */ + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + SECRETS_PATH, + SECRETS_IFACE ("Service"), + "ReadAlias", + g_variant_new ("(s)", "default"), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + unlock_read_alias_cb, + task); + break; + } + } +} + +static void +get_connection_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task; + SecretsServiceData *task_data; + GError *error = NULL; + + task = user_data; + task_data = g_task_get_task_data (task); + + task_data->dbus_connection = g_bus_get_finish (res, &error); + if (task_data->dbus_connection == NULL) + { + g_task_return_error (task, error); + return; + } + + /* Now open a session */ + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + SECRETS_PATH, + SECRETS_IFACE ("Service"), + "OpenSession", + g_variant_new ("(sv)", "plain", + g_variant_new_string ("")), + G_VARIANT_TYPE ("(vo)"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + g_task_get_cancellable (task), + open_session_cb, + task); +} + +/** + * gtk_cups_secrets_service_watch: + * @appeared: The callback to call when the service interface appears + * @vanished: The callback to call when the service interface vanishes + * @user_data: A reference to the watching printbackend + * + * Registers a watch for the secrets service interface. + * + * Returns: The watcher id + */ +guint +gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared, + GBusNameVanishedCallback vanished, + gpointer user_data) +{ + return g_bus_watch_name (G_BUS_TYPE_SESSION, + SECRETS_BUS, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + appeared, + vanished, + user_data, + NULL); +} + +void +cleanup_task_data (gpointer data) +{ + gint i; + SecretsServiceData *task_data = data; + + g_free (task_data->collection_path); + g_strfreev (task_data->auth_info_labels); + g_strfreev (task_data->auth_info_required); + g_free (task_data->printer_uri); + + if (task_data->auth_info != NULL) + { + for (i = 0; task_data->auth_info[i] != NULL; i++) + { + memset (task_data->auth_info[i], 0, strlen (task_data->auth_info[i])); + g_clear_pointer (&task_data->auth_info[i], g_free); + } + g_clear_pointer (&task_data->auth_info, g_free); + } + + if (task_data->prompt_subscription != 0) + { + g_dbus_connection_signal_unsubscribe (task_data->dbus_connection, + task_data->prompt_subscription); + task_data->prompt_subscription = 0; + } + + if (task_data->session_path != NULL) + { + g_dbus_connection_call (task_data->dbus_connection, + SECRETS_BUS, + task_data->session_path, + SECRETS_IFACE ("Session"), + "Close", + NULL, + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + SECRETS_TIMEOUT, + NULL, + NULL, + NULL); + } + + g_clear_object (&task_data->dbus_connection); + g_clear_pointer (&task_data->session_path, g_free); + g_clear_object (&task_data->item_proxy); + g_free (task_data); +} + +/** + * gtk_cups_secrets_service_query_task: + * @source_object: Source object for this task + * @cancellable: Cancellable to cancel this task + * @callback: Callback to call once the query is finished + * @user_data: The user_data passed to the callback + * @printer_uri: URI of the printer + * @auth_info_required: Info required for authentication + * + * Checks if a secrets service as described by the secrets-service standard + * is available and if so it tries to find the authentication info in the + * default collection of the service. + * + * This is the entry point to a chain of async calls to open a session, + * search the secret, unlock the collection (if necessary) and finally + * to lookup the secret. + * + * See: http://standards.freedesktop.org/secret-service/ for documentation + * of the used API. + */ +void +gtk_cups_secrets_service_query_task (gpointer source_object, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const gchar *printer_uri, + gchar **auth_info_required) +{ + GTask *task; + SecretsServiceData *task_data; + + task_data = g_new0 (SecretsServiceData, 1); + task_data->action = SECRETS_SERVICE_ACTION_QUERY; + task_data->printer_uri = g_strdup (printer_uri); + task_data->auth_info_required = g_strdupv (auth_info_required); + + task = g_task_new (source_object, cancellable, callback, user_data); + + g_task_set_task_data (task, task_data, cleanup_task_data); + + g_bus_get (G_BUS_TYPE_SESSION, cancellable, + get_connection_cb, task); +} + +static void +store_done_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task = (GTask *) res; + GError *error = NULL; + + g_task_propagate_pointer (task, &error); + + if (error != NULL) + { + GTK_NOTE (PRINTING, + g_print ("Failed to store auth info: %s\n", error->message)); + g_error_free (error); + } + + g_object_unref (task); + GTK_NOTE (PRINTING, + g_print ("gtk_cups_secrets_service_store finished.\n")); +} + +/** + * gtk_cups_secrets_service_store: + * @auth_info: Auth info that should be stored + * @auth_info_labels: The keys to use for the auth info + * @printer_uri: URI of the printer + * + * Tries to store the auth_info in a secrets service. + */ +void +gtk_cups_secrets_service_store (gchar **auth_info, + gchar **auth_info_labels, + const gchar *printer_uri) +{ + GTask *task; + SecretsServiceData *task_data; + + if (auth_info == NULL || auth_info_labels == NULL || printer_uri == NULL) + { + GTK_NOTE (PRINTING, + g_print ("Invalid call to gtk_cups_secrets_service_store.\n")); + return; + } + + task_data = g_new0 (SecretsServiceData, 1); + task_data->action = SECRETS_SERVICE_ACTION_STORE; + task_data->printer_uri = g_strdup (printer_uri); + task_data->auth_info = g_strdupv (auth_info); + task_data->auth_info_labels = g_strdupv (auth_info_labels); + + task = g_task_new (NULL, NULL, store_done_cb, NULL); + + g_task_set_task_data (task, task_data, cleanup_task_data); + + g_bus_get (G_BUS_TYPE_SESSION, NULL, + get_connection_cb, task); +} diff --git a/modules/printbackends/cups/gtkcupssecretsutils.h b/modules/printbackends/cups/gtkcupssecretsutils.h new file mode 100644 index 000000000..1a0424a3b --- /dev/null +++ b/modules/printbackends/cups/gtkcupssecretsutils.h @@ -0,0 +1,41 @@ +/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords + * Copyright (C) 2014 Intevation GmbH + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ +#ifndef __GTK_SECRETS_UTILS_H__ +#define __GTK_SECRETS_UTILS_H__ + +#include + +#include "gtkcupsutils.h" + +G_BEGIN_DECLS + +void gtk_cups_secrets_service_query_task (gpointer source_object, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + const gchar *printer_uri, + gchar **auth_info_required); +guint gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared, + GBusNameVanishedCallback vanished, + gpointer user_data); +void gtk_cups_secrets_service_store (gchar **auth_info, + gchar **auth_info_labels, + const gchar *printer_uri); + +G_END_DECLS + +#endif /* __GTK_SECRETS_UTILS_H__ */ diff --git a/modules/printbackends/cups/gtkcupsutils.c b/modules/printbackends/cups/gtkcupsutils.c index fdfe21e91..18eedad39 100644 --- a/modules/printbackends/cups/gtkcupsutils.c +++ b/modules/printbackends/cups/gtkcupsutils.c @@ -34,6 +34,11 @@ typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request); +static http_t *gtk_cups_http_connect (const char *host, + int port, + http_encryption_t encryption); +static int gtk_cups_http_reconnect (http_t *http); + static void _connect (GtkCupsRequest *request); static void _post_send (GtkCupsRequest *request); static void _post_write_request (GtkCupsRequest *request); @@ -171,7 +176,7 @@ gtk_cups_request_new_with_username (http_t *connection, else { request->http = NULL; - request->http = httpConnectEncrypt (request->server, + request->http = gtk_cups_http_connect (request->server, ippPort (), cupsEncryption ()); @@ -681,6 +686,29 @@ gtk_cups_request_set_ipp_version (GtkCupsRequest *request, ippSetVersion (request->ipp_request, major, minor); } + +static http_t * +gtk_cups_http_connect (const char *host, + int port, + http_encryption_t encryption) +{ +#ifdef HAVE_HTTPCONNECT2 + return httpConnect2 (host, port, NULL, AF_UNSPEC, encryption, 0, 30000, NULL); +#else + return httpConnectEncrypt (host, port, encryption); +#endif +} + +static int +gtk_cups_http_reconnect (http_t *http) +{ +#ifdef HAVE_HTTPRECONNECT2 + return httpReconnect2 (http, 30000, NULL); +#else + return httpReconnect (http); +#endif +} + static void _connect (GtkCupsRequest *request) { @@ -689,7 +717,7 @@ _connect (GtkCupsRequest *request) if (request->http == NULL) { - request->http = httpConnectEncrypt (request->server, + request->http = gtk_cups_http_connect (request->server, ippPort (), cupsEncryption ()); @@ -744,7 +772,7 @@ _post_send (GtkCupsRequest *request) if (httpPost (request->http, request->resource)) { - if (httpReconnect (request->http)) + if (gtk_cups_http_reconnect (request->http)) { request->state = GTK_CUPS_POST_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; @@ -1046,7 +1074,7 @@ _post_check (GtkCupsRequest *request) } if (auth_result || - httpReconnect (request->http)) + gtk_cups_http_reconnect (request->http)) { /* if the password has been used, reset password_state * so that we ask for a new one next time around @@ -1105,7 +1133,7 @@ _post_check (GtkCupsRequest *request) request->state = GTK_CUPS_POST_CONNECT; /* Reconnect... */ - httpReconnect (request->http); + gtk_cups_http_reconnect (request->http); /* Upgrade with encryption... */ httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); @@ -1232,7 +1260,7 @@ _get_send (GtkCupsRequest *request) if (httpGet (request->http, request->resource)) { - if (httpReconnect (request->http)) + if (gtk_cups_http_reconnect (request->http)) { request->state = GTK_CUPS_GET_DONE; request->poll_state = GTK_CUPS_HTTP_IDLE; @@ -1341,7 +1369,7 @@ _get_check (GtkCupsRequest *request) } if (auth_result || - httpReconnect (request->http)) + gtk_cups_http_reconnect (request->http)) { /* if the password has been used, reset password_state * so that we ask for a new one next time around @@ -1373,7 +1401,7 @@ _get_check (GtkCupsRequest *request) request->state = GTK_CUPS_GET_CONNECT; /* Reconnect... */ - httpReconnect (request->http); + gtk_cups_http_reconnect (request->http); /* Upgrade with encryption... */ httpEncryption (request->http, HTTP_ENCRYPT_REQUIRED); diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c index e17d97d21..21ea33a23 100644 --- a/modules/printbackends/cups/gtkprintbackendcups.c +++ b/modules/printbackends/cups/gtkprintbackendcups.c @@ -60,6 +60,11 @@ #include "gtkprintercups.h" #include "gtkcupsutils.h" +#include "gtkcupssecretsutils.h" + +#define GTK_PRINT_BACKEND_CAN_STORE_AUTH_INFO_KEY "gtk-print-backend-can-store-auth-info" +#define GTK_PRINT_BACKEND_STORE_AUTH_INFO_KEY "gtk-print-backend-store-auth-info" +#define GTK_PRINT_BACKEND_AUTH_PRINTER_URI_KEY "gtk-print-backend-auth-printer-uri" typedef struct _GtkPrintBackendCupsClass GtkPrintBackendCupsClass; @@ -144,6 +149,9 @@ struct _GtkPrintBackendCups GHashTable *auth; gchar *username; gboolean authentication_lock; + + gboolean secrets_service_available; + guint secrets_service_watch_id; #ifdef HAVE_CUPS_API_1_6 GDBusConnection *dbus_connection; gchar *avahi_default_printer; @@ -213,6 +221,14 @@ static cairo_surface_t * cups_printer_create_cairo_surface (GtkPrinter static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend, gchar **auth_info_required, gchar **auth_info); +static void secrets_service_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data); +static void secrets_service_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data); + void overwrite_and_free (gpointer data); static gboolean is_address_local (const gchar *address); @@ -489,6 +505,22 @@ cups_free_print_stream_data (CupsPrintStreamData *data) g_free (data); } + +static http_t * +gtk_cups_backend_http_connect (const char *host, + int port, + http_encryption_t encryption) +{ +#ifdef HAVE_HTTPCONNECT2 + return httpConnect2 (host, port, NULL, AF_UNSPEC, encryption, 1, 30000, NULL); +#else + if (encryption == HTTP_ENCRYPT_NEVER) + return httpConnect (host, port); + + return httpConnectEncrypt (host, port, encryption); +#endif +} + static void cups_print_cb (GtkPrintBackendCups *print_backend, GtkCupsResult *result, @@ -636,7 +668,7 @@ gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend, { http_t *http; - http = httpConnect (cups_printer->hostname, cups_printer->port); + http = gtk_cups_backend_http_connect (cups_printer->hostname, cups_printer->port, HTTP_ENCRYPT_NEVER); if (http) { request = gtk_cups_request_new_with_username (http, @@ -751,6 +783,27 @@ void overwrite_and_free (gpointer data) } } +static void +secrets_service_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (user_data); + + cups_backend->secrets_service_available = TRUE; +} + +static void +secrets_service_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (user_data); + + cups_backend->secrets_service_available = FALSE; +} + static void gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups) { @@ -767,6 +820,11 @@ gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups) backend_cups->requests = NULL; backend_cups->auth = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, overwrite_and_free); backend_cups->authentication_lock = FALSE; + backend_cups->secrets_service_available = FALSE; + backend_cups->secrets_service_watch_id = + gtk_cups_secrets_service_watch (secrets_service_appeared, + secrets_service_vanished, + backend_cups); backend_cups->covers = NULL; backend_cups->number_of_covers = 0; @@ -836,6 +894,12 @@ gtk_print_backend_cups_dispose (GObject *object) backend_cups = GTK_PRINT_BACKEND_CUPS (object); + if (backend_cups->secrets_service_watch_id != 0) + { + g_bus_unwatch_name (backend_cups->secrets_service_watch_id); + backend_cups->secrets_service_watch_id = 0; + } + if (backend_cups->list_printers_poll > 0) g_source_remove (backend_cups->list_printers_poll); backend_cups->list_printers_poll = 0; @@ -940,7 +1004,7 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend, GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend); GList *l; char dispatch_hostname[HTTP_MAX_URI]; - gchar *key; + gchar *key = NULL; gchar *username = NULL; gchar *hostname = NULL; gchar *password = NULL; @@ -962,15 +1026,27 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend, if (hostname != NULL && username != NULL && password != NULL) { + const gchar *printer_uri; + gboolean store_auth_info; + key = g_strconcat (username, "@", hostname, NULL); g_hash_table_insert (cups_backend->auth, key, g_strdup (password)); + + printer_uri = g_object_get_data (G_OBJECT (backend), + GTK_PRINT_BACKEND_AUTH_PRINTER_URI_KEY); + store_auth_info = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (backend), + GTK_PRINT_BACKEND_STORE_AUTH_INFO_KEY)); + + if (store_auth_info && cups_backend->secrets_service_available && printer_uri != NULL) + gtk_cups_secrets_service_store (auth_info, auth_info_required, printer_uri); } g_free (cups_backend->username); cups_backend->username = g_strdup (username); - GTK_NOTE (PRINTING, - g_print ("CUPS backend: storing password for %s\n", key)); + if (key != NULL) + GTK_NOTE (PRINTING, + g_print ("CUPS backend: storing password for %s\n", key)); for (l = cups_backend->requests; l; l = l->next) { @@ -1114,6 +1190,14 @@ request_password (gpointer data) g_free (printer_name); + g_object_set_data (G_OBJECT (dispatch->backend), + GTK_PRINT_BACKEND_CAN_STORE_AUTH_INFO_KEY, + GINT_TO_POINTER (dispatch->backend->secrets_service_available)); + g_object_set_data_full (G_OBJECT (dispatch->backend), + GTK_PRINT_BACKEND_AUTH_PRINTER_URI_KEY, + g_strdup (printer_uri), + g_free); + g_signal_emit_by_name (dispatch->backend, "request-password", auth_info_required, auth_info_default, auth_info_display, auth_info_visible, prompt); @@ -2555,7 +2639,7 @@ cups_request_avahi_printer_info (const gchar *printer_uri, AvahiService *service; http_t *http; - http = httpConnect (host, port); + http = gtk_cups_backend_http_connect (host, port, HTTP_ENCRYPT_NEVER); if (http) { service = (AvahiService *) g_new0 (AvahiService, 1); @@ -3438,7 +3522,7 @@ cups_request_ppd (GtkPrinter *printer) } } - http = httpConnectEncrypt (cups_printer->hostname, + http = gtk_cups_backend_http_connect (cups_printer->hostname, cups_printer->port, cupsEncryption ()); diff --git a/po/de.po b/po/de.po index 49e158bfb..360aabc76 100644 --- a/po/de.po +++ b/po/de.po @@ -2743,7 +2743,7 @@ msgstr "_Aufsteigend" #: ../gtk/gtkstock.c:427 msgctxt "Stock label" msgid "_Descending" -msgstr "_Absteigend" +msgstr "A_bsteigend" #: ../gtk/gtkstock.c:428 msgctxt "Stock label" @@ -6086,3 +6086,95 @@ msgstr "" #~ msgid "X" #~ msgstr "X" + +#: gtk/gtkfilechooserdefault.c +msgid "A file named \"%s\" already exists." +msgstr "Eine Datei namens „%s“ existiert bereits." + +#: gtk/gtkfilechooserdefault.c +msgid "Choose a new name for the pasted file." +msgstr "Neuen Namen für die eingefügte Datei wählen." + +#: gtk/gtkfilechooserdefault.c +msgid "New name:" +msgstr "Neuer Name:" + +#: gtk/gtkfilechooserdefault.c +msgid "Open _Containing Folder" +msgstr "_Dateipfad öffnen" + +#: gtk/gtkfilechooserdefault.c +msgid "Could not paste files" +msgstr "Dateien konnten nicht eingefügt werden" + +#: gtk/gtkfilechooserdefault.c +msgid "Could not move %s" +msgstr "%s konnte nicht verschoben werden" + +#: gtk/gtkfilechooserdefault.c +msgid "Could not copy %s" +msgstr "%s konnte nicht kopiert werden" + +#: gtk/gtkfilechooserdefault.c +msgid "Could not move %s to the trash" +msgstr "%s konnte nicht in den Papierkorb verschoben werden" + +#: gtk/gtkfilechooserdefault.c +msgid "Could not move files to the trash" +msgstr "Dateien konnten nicht in den Papierkorb verschoben werden" + +#: gtk/gtkfilechooserdefault.c +msgid "_Copy Location" +msgstr "_Pfad kopieren" + +#: gtk/gtkfilechooserdefault.c +msgid "_Rename..." +msgstr "_Umbenennen …" + +#: gtk/gtkfilechooserdefault.c +msgid "Move to _Trash" +msgstr "In den _Papierkorb verschieben" + +#: gtk/gtkfilechooserdefault.c +msgid "Show _Preview Panel" +msgstr "_Vorschauleiste anzeigen" + +#: gtk/gtkfilechooserdefault.c +msgid "Show _Thumbnails" +msgstr "_Vorschaubilder anzeigen" + +#: gtk/gtkfilechooserdefault.c +msgid "List View" +msgstr "Listenansicht" + +#: gtk/gtkfilechooserdefault.c +msgid "Icon View" +msgstr "Symbolansicht" + +#: gtk/gtkfilechooserdefault.c +msgid "Compact View" +msgstr "Kompaktansicht" + +#: gtk/gtkfilechooserdefault.c +msgid "Show Preview Panel" +msgstr "Vorschauleiste anzeigen" + +#: gtk/gtkfilechooserdefault.c +msgid "Arrange Items" +msgstr "Objekte anordnen" + +#: gtk/gtkfilechooserdefault.c +msgid "Arrange _Items" +msgstr "_Objekte anordnen" + +#: gtk/gtkfilechooserdefault.c +msgid "By _Name" +msgstr "Nach _Name" + +#: gtk/gtkfilechooserdefault.c +msgid "By _Size" +msgstr "Nach _Größe" + +#: gtk/gtkfilechooserdefault.c +msgid "By _Modification Date" +msgstr "Nach _Änderungsdatum" diff --git a/tests/testtreeview.c b/tests/testtreeview.c index 12c0d3652..7ba8ba39f 100644 --- a/tests/testtreeview.c +++ b/tests/testtreeview.c @@ -628,9 +628,9 @@ create_tree_model (void) gint i; GType *t; volatile GType dummy G_GNUC_UNUSED; /* G_GNUC_CONST makes the optimizer remove - * get_type calls if you don't do something - * like this - */ + * get_type calls if you don't do something + * like this + */ /* Make the tree more interesting */ dummy = gtk_scrolled_window_get_type ();