flashcards

Stupid LaTeX flashcard viewer
git clone git://lumidify.org/flashcards.git (fast, but not encrypted)
git clone https://lumidify.org/flashcards.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/flashcards.git (over tor)
Log | Files | Refs | README

viewer2.pl (6859B)


      1 #!/usr/bin/env perl
      2 
      3 use strict;
      4 use warnings;
      5 use utf8;
      6 use open qw< :encoding(UTF-8) >;
      7 binmode(STDOUT, ":utf8");
      8 use Gtk2 '-init';
      9 use Glib qw/TRUE FALSE/;
     10 use JSON qw(decode_json encode_json);
     11 use List::Util qw(min);
     12 
     13 sub load_cards {
     14 	my %cards;
     15 	my %cards_backreference;
     16 	opendir my $dir, "latex" or die "Unable to open flashcards directory.\n";
     17 	while (my $filename = readdir($dir)) {
     18 		next if ($filename =~ /\A\.\.?\z/);
     19 		open my $fh, "<", "latex/$filename" || die "Unable to open file \"latex/$filename\"\n";
     20 		my $line = <$fh>;
     21 		$line =~ /\|(.*)$/;
     22 		$cards{$filename} = $1;
     23 		$cards_backreference{$1} = $filename if $1;
     24 		if ($1 && !-f "latex/$1") {
     25 			die "Flashcard \"$1\" mentioned in \"$filename\" does not exist.\n";
     26 		}
     27 		close $fh;
     28 	}
     29 	closedir $dir;
     30 	return \%cards, \%cards_backreference;
     31 }
     32 
     33 sub load_config {
     34 	my $path = shift;
     35 	my $config;
     36 	if (-e $path) {
     37 		open my $fh, "<", $path or die "Unable to open config \"$config\".\n";
     38 		my $json_raw = do {local $/; <$fh>};
     39 		$config = decode_json $json_raw;
     40 		close $fh;
     41 	} else {
     42 		$config = {};
     43 	}
     44 	return $config;
     45 }
     46 
     47 sub remove_cruft {
     48 	my ($config, $cards) = @_;
     49 	my $clean_config;
     50 	my $max_view_count = 0;
     51 	foreach my $card (keys %$cards) {
     52 		if (exists $config->{cards}->{$card}) {
     53 			$clean_config->{cards}->{$card} = $config->{cards}->{$card};
     54 		} else {
     55 			$clean_config->{cards}->{$card} = 0;
     56 		}
     57 	}
     58 	return $clean_config;
     59 }
     60 
     61 sub sort_cards {
     62 	my ($config, $cards) = @_;
     63 	my $sorted_cards;
     64 	foreach my $card (keys %$cards) {
     65 		my $view_count = $config->{cards}->{$card};
     66 		$sorted_cards->{$view_count}->{$card} = $cards->{$card};
     67 	}
     68 	return $sorted_cards;
     69 }
     70 
     71 sub get_rand_card {
     72 	my $cards = shift;
     73 	my $min_view_count = min keys %$cards;
     74 	my @card_ids = keys %{$cards->{$min_view_count}};
     75 	my $card = $card_ids[rand @card_ids];
     76 	return ($min_view_count, $card);
     77 }
     78 
     79 sub find_card_view_count {
     80 	my ($cards, $card_id) = @_;
     81 	foreach my $view_count (keys %$cards) {
     82 		return $view_count if exists $cards->{$view_count}->{$card_id};
     83 	}
     84 }
     85 
     86 sub load_images_front {
     87 	my ($image_front1, $image_front2, $window_w, $card_id) = @_;
     88 	my ($fmt, $w, $h) = Gtk2::Gdk::Pixbuf->get_file_info("cache/${card_id}_front1.png");
     89 	$w = $w < $window_w ? $w : $window_w;
     90 	my $pixbuf1 = Gtk2::Gdk::Pixbuf->new_from_file_at_size("cache/${card_id}_front1.png", $w, -1);
     91 	($fmt, $w, $h) = Gtk2::Gdk::Pixbuf->get_file_info("cache/${card_id}_front2.png");
     92 	$w = $w < $window_w ? $w : $window_w;
     93 	my $pixbuf2 = Gtk2::Gdk::Pixbuf->new_from_file_at_size("cache/${card_id}_front2.png", $w, -1);
     94 	$image_front1->set_from_pixbuf($pixbuf1);
     95 	$image_front2->set_from_pixbuf($pixbuf2);
     96 }
     97 
     98 sub load_image_back {
     99 	my ($image_back, $window_w, $image_back_h, $card_id) = @_;
    100 	my ($fmt, $w, $h) = Gtk2::Gdk::Pixbuf->get_file_info("cache/${card_id}_back.png");
    101 	my $w_final = $w < $window_w ? $w : $window_w;
    102 	my $h_final = $h < $image_back_h ? $h : $image_back_h;
    103 	my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file_at_size("cache/${card_id}_back.png", $w_final, $h_final);
    104 	$image_back->set_from_pixbuf($pixbuf);
    105 }
    106 
    107 sub next_card {
    108 	my ($config, $cards, $cards_backreference, $image_front1, $image_front2, $window_w, $window, $cur_card_id) = @_;
    109 	my ($view_count, $card_id);
    110 	if ($cur_card_id && ($card_id = $cards_backreference->{$cur_card_id})) {
    111 		$view_count = find_card_view_count $cards, $card_id;
    112 	} else {
    113 		($view_count, $card_id) = get_rand_card $cards;
    114 		while (my $req_card = $cards->{$view_count}->{$card_id}) {
    115 			$card_id = $req_card;
    116 			# efficiency is for sissies
    117 			$view_count = find_card_view_count $cards, $card_id;
    118 		}
    119 	}
    120 	$window->set_title($card_id);
    121 	load_images_front $image_front1, $image_front2, $window_w, $card_id;
    122 	return ($view_count, $card_id);
    123 }
    124 
    125 sub gui {
    126 	my $config = load_config("config.json");
    127 	my ($cards, $cards_backreference) = load_cards;
    128 	$config = remove_cruft $config, $cards;
    129 	$cards = sort_cards $config, $cards;
    130 
    131         my $window = Gtk2::Window->new('toplevel');
    132 	$window->signal_connect(delete_event => sub {return FALSE});
    133 	$window->signal_connect(destroy => sub { Gtk2->main_quit; });
    134 	my $window_w = 0;
    135 	my $image_back_h = 0;
    136 	$window->signal_connect(configure_event => sub {
    137 		my ($window, $event) = @_;
    138 		# subtract 20 to account for window border width
    139 		$window_w = $event->width - 20;
    140 		return FALSE;
    141 	});
    142 	$window->set_border_width(10);
    143 
    144 	my $view_count;
    145 	my $card_id;
    146 
    147 	my $vbox = Gtk2::VBox->new(FALSE, 5);
    148 	my $hbox = Gtk2::HBox->new(FALSE, 5);
    149 
    150 	my $image_front1 = Gtk2::Image->new();
    151 	my $image_front2 = Gtk2::Image->new();
    152 	my $image_back = Gtk2::Image->new();
    153 	$image_back->signal_connect(size_allocate => sub {
    154 		my ($image, $event) = @_;
    155 		$image_back_h = $event->height;
    156 		return FALSE;
    157 	}, $window);
    158 
    159 	my $revealed = 0;
    160 	my $label = Gtk2::Label->new("");
    161 	my $button = Gtk2::Button->new_with_mnemonic("_Next card");
    162 	$button->signal_connect(clicked => sub {
    163 		if ($revealed) {
    164 			$config->{cards}->{$card_id}++;
    165 			$cards->{$view_count+1}->{$card_id} = $cards->{$view_count}->{$card_id};
    166 			delete $cards->{$view_count}->{$card_id};
    167 			if (!%{$cards->{$view_count}}) {
    168 				delete $cards->{$view_count};
    169 			}
    170 		}
    171 		$revealed = 0;
    172 		($view_count, $card_id) = next_card $config, $cards, $cards_backreference, $image_front1, $image_front2, $window_w, $window, $card_id;
    173 		$label->set_text("View count: " . $view_count);
    174 		$image_back->clear();
    175 	}, $window);
    176 	$hbox->pack_start($button, FALSE, FALSE, 0);
    177 	$button = Gtk2::Button->new_with_mnemonic("Next (no _view increase)");
    178 	$button->signal_connect(clicked => sub {
    179 		$revealed = 0;
    180 		($view_count, $card_id) = next_card $config, $cards, $cards_backreference, $image_front1, $image_front2, $window_w, $window, $card_id;
    181 		$label->set_text("View count: " . $view_count);
    182 		$image_back->clear();
    183 	}, $window);
    184 	$hbox->pack_start($button, FALSE, FALSE, 0);
    185 	$button = Gtk2::Button->new_with_mnemonic("Reveal _back");
    186 	$button->signal_connect(clicked => sub {
    187 		if (!$revealed) {
    188 			load_image_back $image_back, $window_w, $image_back_h, $card_id;
    189 			$revealed = 1;
    190 		}
    191 	}, $window);
    192 	$hbox->pack_start($button, FALSE, FALSE, 0);
    193 	$button = Gtk2::Button->new_with_mnemonic("_Reload images");
    194 	$button->signal_connect(clicked => sub {
    195 		load_images_front $image_front1, $image_front2, $window_w, $card_id;
    196 		if ($revealed) {
    197 			load_image_back $image_back, $window_w, $image_back_h, $card_id;
    198 		}
    199 	}, $window);
    200 	$hbox->pack_start($button, FALSE, FALSE, 0);
    201 	$hbox->pack_start($label, FALSE, FALSE, 0);
    202 	$vbox->pack_start($hbox, FALSE, FALSE, 0);
    203 	$vbox->pack_start($image_front1, FALSE, FALSE, 0);
    204 	$vbox->pack_start($image_front2, FALSE, FALSE, 0);
    205 	$vbox->pack_start($image_back, TRUE, TRUE, 0);
    206 	$window->add($vbox);
    207 	$window->show_all;
    208 	Gtk2->main;
    209 	open my $fh, ">", "config.json" or die "Unable to save config.\n";
    210 	my $json_encoded = encode_json $config;
    211 	print $fh "$json_encoded\n";
    212 	close $fh;
    213 }
    214 
    215 gui;