@@ -29,6 +29,11 @@ namespace Notejot {
2929
3030 private Gtk . Label places_subtitle_number;
3131
32+ // Places (sidebar card) map preview
33+ private Shumate . SimpleMap places_map_widget;
34+ private Shumate . MapSourceRegistry places_registry;
35+ private Shumate . MarkerLayer places_marker_layer;
36+
3237 public Sidebar (DataManager data_manager ) {
3338 Object (
3439 orientation: Gtk . Orientation . VERTICAL ,
@@ -100,13 +105,9 @@ namespace Notejot {
100105 this.insights_card_overlay.add_overlay (this .insights_switch );
101106 this.append (this .insights_card_overlay );
102107
103- // Places card (keeps existing simple counter)
104- this . places_button = create_sidebar_card (
105- _ ("Places "),
106- _ ("0 "),
107- "places-card",
108- out this.places_subtitle_number
109- );
108+ // Places card
109+ // Places card with non-interactive map preview
110+ this . places_button = create_places_card_with_map (out this .places_subtitle_number );
110111 places_button.set_size_request (-1 , 148 );
111112 places_button.clicked.connect (() => {
112113 if (editing_mode)return ;
@@ -250,40 +251,117 @@ namespace Notejot {
250251 this . insights_words_number_label. set_label (@" $words_all_time " );
251252 }
252253 this . places_subtitle_number. set_label (@" $location_count " );
254+ this . refresh_places_preview_pins ();
255+
256+ this . places_map_widget. queue_draw ();
253257 }
254258
255- private Gtk .Button create_sidebar_card ( string title , string subtitle , string style_class , out Gtk .Label number_label_out ) {
259+ private Gtk .Button create_places_card_with_map ( out Gtk .Label number_label_out ) {
256260 var button = new Gtk .Button ();
257261 button. add_css_class (" card" );
258- button. add_css_class (style_class);
262+ button. add_css_class (" places-card" );
263+ button. set_overflow (Gtk . Overflow . HIDDEN );
264+
265+ // Overlay with map as background
266+ var overlay = new Gtk .Overlay ();
267+ overlay. set_vexpand_set (true );
268+ button. set_child (overlay);
269+
270+ this . places_map_widget = new Shumate .SimpleMap ();
271+ this . places_map_widget. set_hexpand (true );
272+ this . places_map_widget. show_zoom_buttons = false ;
273+ this . places_map_widget. license. visible = false ;
274+ this . places_map_widget. scale. visible = false ;
275+ // Make the map non-interactive inside the sidebar card
276+ this . places_map_widget. set_can_target (false );
277+ this . places_map_widget. set_focusable (false );
278+ this . places_map_widget. realize. connect (() = > {
279+ this . fit_places_preview_to_markers ();
280+ this . places_map_widget. queue_draw ();
281+ });
282+
283+ // Map source and layers
284+ setup_places_preview_map_source ();
285+ this . places_marker_layer = new Shumate .MarkerLayer (this . places_map_widget. get_viewport ());
286+ this . places_map_widget. add_overlay_layer (this . places_marker_layer);
287+
288+ overlay. set_child (this . places_map_widget);
259289
260- var box = new Gtk .Box (Gtk . Orientation . VERTICAL , 0 ) { halign = Gtk . Align . START , hexpand = true };
261- button. set_child (box);
290+ // Title and counter overlay at top-left
291+ var box = new Gtk .Box (Gtk . Orientation . VERTICAL , 0 ) { halign = Gtk . Align . START };
292+ box. set_size_request (165 , - 1 );
262293
263294 var title_box = new Gtk .Box (Gtk . Orientation . HORIZONTAL , 0 );
264295 box. append (title_box);
265296
266- var title_label = new Gtk .Label (title ) { hexpand = true };
297+ var title_label = new Gtk .Label (_( " Places " ) ) { hexpand = true };
267298 title_label. add_css_class (" title" );
268299 title_label. set_xalign (0.0f );
269300 title_box. append (title_label);
270301
271302 var subtitle_box = new Gtk .Box (Gtk . Orientation . HORIZONTAL , 4 );
272- box. append (subtitle_box);
273-
274303 var image = new Gtk .Image .from_icon_name (" location-active-symbolic" );
275304 image. pixel_size = 24 ;
276305 subtitle_box. append (image);
277306
278- string number = subtitle;
279- number_label_out = new Gtk .Label (number);
307+ number_label_out = new Gtk .Label (" 0" );
280308 number_label_out. add_css_class (" stat-value" );
281309 number_label_out. set_xalign (0.0f );
282310 subtitle_box. append (number_label_out);
283311
312+ box. append (subtitle_box);
313+ overlay. add_overlay (box);
314+
315+ // Initial pins
316+ refresh_places_preview_pins ();
317+
284318 return button;
285319 }
286320
321+ private void setup_places_preview_map_source () {
322+ this . places_registry = new Shumate .MapSourceRegistry .with_defaults ();
323+
324+ if (Shumate . VectorRenderer . is_supported ()) {
325+ try {
326+ var style_json = new Notejot .MapStyle (" notejot-light" ). to_string ();
327+ var renderer = new Shumate .VectorRenderer (" notejot-light" , style_json) {
328+ max_zoom_level = 14 ,
329+ min_zoom_level = 1
330+ };
331+ this . places_registry. add (renderer);
332+ } catch (Error e) {
333+ warning (" Places sidebar map: failed to create vector map style: %s " , e. message);
334+ }
335+ }
336+
337+ NotejotApp . settings. bind_with_mapping (
338+ " map-source" , this . places_map_widget, " map-source" , GET ,
339+ (SettingsBindGetMappingShared ) PlacesView . map_source_get_mapping_cb,
340+ (SettingsBindSetMappingShared ) null ,
341+ this . places_registry, null
342+ );
343+ }
344+
345+ private void refresh_places_preview_pins () {
346+ if (this . places_marker_layer == null ) {
347+ return ;
348+ }
349+ this . places_marker_layer. remove_all ();
350+
351+ var unique_locations = this . data_manager. get_unique_locations ();
352+ foreach (var entry in unique_locations) {
353+ var marker = new Shumate .Marker ();
354+ var marker_image = new Gtk .Image .from_icon_name (" marker-pin" ) { pixel_size = 20 };
355+ marker. set_child (marker_image);
356+ marker. set_location (entry. latitude, entry. longitude);
357+ this . places_marker_layer. add_marker (marker);
358+ }
359+ if (this . places_map_widget != null ) {
360+ this . fit_places_preview_to_markers ();
361+ this . places_map_widget. queue_draw ();
362+ }
363+ }
364+
287365 private Gtk .Button create_insights_card (out Gtk .Label year_number_out ,
288366 out Gtk .Label days_number_out ,
289367 out Gtk .Label words_number_out ) {
@@ -368,6 +446,68 @@ namespace Notejot {
368446 return button;
369447 }
370448
449+ private void fit_places_preview_to_markers () {
450+ if (this . places_map_widget == null ) {
451+ return ;
452+ }
453+
454+ var viewport = this . places_map_widget. get_viewport ();
455+ var unique_locations = this . data_manager. get_unique_locations ();
456+
457+ int count = 0 ;
458+ double min_lat = 0.0 ;
459+ double max_lat = 0.0 ;
460+ double min_lon = 0.0 ;
461+ double max_lon = 0.0 ;
462+
463+ foreach (var e in unique_locations) {
464+ double lat = (double ) e. latitude;
465+ double lon = (double ) e. longitude;
466+ if (count == 0 ) {
467+ min_lat = lat;
468+ max_lat = lat;
469+ min_lon = lon;
470+ max_lon = lon;
471+ } else {
472+ if (lat < min_lat)min_lat = lat;
473+ if (lat > max_lat)max_lat = lat;
474+ if (lon < min_lon)min_lon = lon;
475+ if (lon > max_lon)max_lon = lon;
476+ }
477+ count++ ;
478+ }
479+
480+ if (count == 0 ) {
481+ viewport. zoom_level = 1.0 ;
482+ viewport. latitude = 0.0 ;
483+ viewport. longitude = 0.0 ;
484+ return ;
485+ }
486+
487+ double center_lat = (min_lat + max_lat) / 2.0 ;
488+ double center_lon = (min_lon + max_lon) / 2.0 ;
489+ viewport. latitude = center_lat;
490+ viewport. longitude = center_lon;
491+
492+ if (count == 1 ) {
493+ viewport. zoom_level = 12.0 ;
494+ return ;
495+ }
496+
497+ double span_lat = max_lat - min_lat;
498+ double span_lon = max_lon - min_lon;
499+ double span = (span_lat > span_lon) ? span_lat : span_lon;
500+
501+ double zoom = 3.0 ;
502+ if (span < 0.01 )zoom = 13.0 ;
503+ else if (span < 0.05 )zoom = 12.0 ;
504+ else if (span < 0.1 )zoom = 11.0 ;
505+ else if (span < 0.5 )zoom = 10.0 ;
506+ else if (span < 1.0 )zoom = 9.0 ;
507+
508+ viewport. zoom_level = zoom;
509+ }
510+
371511 private GLib .List<Tag ?> sort_tags (GLib .List<Tag ?> tags , string [] order ) {
372512 var by_uuid = new GLib .HashTable<string, Tag ?> (GLib . str_hash, GLib . str_equal);
373513 foreach (var t in tags) {
@@ -376,6 +516,7 @@ namespace Notejot {
376516 }
377517 }
378518
519+
379520 var used = new GLib .HashTable<string, bool> (GLib . str_hash, GLib . str_equal);
380521 var result = new GLib .List<Tag ?> ();
381522
0 commit comments