URI:
       viewer2.pl - flashcards - Stupid LaTeX flashcard viewer
  HTML git clone git://lumidify.org/flashcards.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/flashcards.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/flashcards.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR 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;