App-InvestSim
view release on metacpan or search on metacpan
lib/App/InvestSim/GUI.pm view on Meta::CPAN
$frame->new_ttk__label(-text => "Plafond du loyer :")
->g_grid(-column => 0, -row => $row, -sticky => "e", -padx => "0 2");
($rent_cap_entry = $frame->new_ttk__entry(-width => ENTRY_WIDTH, -state => 'readonly', -textvariable => \$rent_cap_text, -takefocus => 0))
->g_grid(-column => 1, -row => $row++, -sticky => "we", -pady => 2);
$frame->new_ttk__label(-text => "Valeur déductible du bien :")
->g_grid(-column => 0, -row => $row, -sticky => "e", -padx => "0 2");
$frame->new_ttk__entry(-width => ENTRY_WIDTH, -state => 'readonly', -textvariable => \$pinel_worth_text, -takefocus => 0)
->g_grid(-column => 1, -row => $row++, -sticky => "we", -pady => 2);
$frame->new_ttk__label(-text => "Frais de notaire :")
->g_grid(-column => 0, -row => $row, -sticky => "e", -padx => "0 2");
$frame->new_ttk__entry(-width => ENTRY_WIDTH, -state => 'readonly', -textvariable => \$notary_fees_text, -takefocus => 0)
->g_grid(-column => 1, -row => $row++, -sticky => "we", -pady => 2);
$frame->new_ttk__label(-text => "Montant total investi :")
->g_grid(-column => 0, -row => $row, -sticky => "e", -padx => "0 2");
$frame->new_ttk__entry(-width => ENTRY_WIDTH, -state => 'readonly', -textvariable => \$total_invested_text, -takefocus => 0)
->g_grid(-column => 1, -row => $row++, -sticky => "we", -pady => 2);
}
# Build the bottom table.
$root->g_grid_columnconfigure(1, -weight => 1);
$root->g_grid_rowconfigure(3, -weight => 1);
{
my $frame = $root->new_ttk__frame(-padding => 3, -height => 550);
$frame->g_grid_propagate(0);
$frame->g_grid(-column => 0, -row => 3, -columnspan => 3, -sticky => "nwes");
$frame->g_grid_columnconfigure(0, -weight => 1);
$frame->g_grid_rowconfigure(0, -weight => 1);
$display_table = $frame->new_ttk__treeview(-height => ($values{duration} // 20) + 2);
$display_table->g_grid(-column => 0, -row => 0, -rowspan => 2, -sticky => "nwes");
# We're setting a specific font for items with the tag 'total'.
$display_table->m_tag('configure', 'total', -font => $treeview_total_font);
#my $hscroll = $frame->new_tk__scrollbar(-orient => "horizontal", -command => [$display_table, "xview"]);
#$hscroll->g_grid(-column => 0, -row => 1, -sticky => "we");
my $vscroll = $frame->new_ttk__scrollbar(-orient => "vertical", -command => [$display_table, "yview"]);
$vscroll->g_grid(-column => 1, -row => 0, -sticky => "ns");
$frame->new_ttk__sizegrip()->g_grid(-column => 1, -row => 1, -sticky => "se");
$display_table->configure(-yscrollcommand => [$vscroll, "set"]);
#$display_table->configure(-xscrollcommand => [$hscroll, "set"]);
my @headings = ('Année', 'Loyer net', 'Placements', 'Principal du prêt', 'Intérêts du prêt', 'Frais du prêt', 'Revenus imposable', 'Déficit déductible', 'Impôt', 'Solde annuel', 'Capital');
# We're not using the name of the columns (c1, c2, ...) we're only using their
# index (#0, #1, ...), including #0 the index of the first implicit column.
$display_table->m_configure(-columns => [map { "c$_" } 1..$#headings ]);
for my $c (0..$#headings) {
my $width = Tkx::font_measure(Tkx::ttk__style_lookup('Heading', '-font'), $headings[$c]);
$display_table->m_heading("#${c}", -text => $headings[$c]);
$display_table->m_column("#${c}", -width => $width, -anchor => 'e');
}
}
# Finally, we create a small menu.
{
my $menu = $root->new_menu;
$root->configure(-menu => $menu);
my $file = $menu->new_menu;
$menu->m_add_cascade(-menu => $file, -label => "Fichier", -underline => 0);
$file->m_add_command(-label => "Nouveau", -accelerator => 'Ctrl+N', -underline => 0,
-command => sub { init_values(); refresh_all_fields() });
$root->g_bind('<Control-n>', sub { init_values(); refresh_all_fields() });
$file->m_add_command(-label => "Ouvrir...", -accelerator => 'Ctrl+O', -underline => 0,
-command => sub { open_values(); refresh_all_fields() });
$root->g_bind('<Control-o>', sub { open_values(); refresh_all_fields() });
$file->m_add_command(-label => "Enregistrer", -accelerator => 'Ctrl+S', -underline => 0, -command => \&save_values);
$root->g_bind('<Control-s>', \&save_values);
$file->m_add_command(-label => "Enregistrer sous...", -accelerator => 'Ctrl+Alt+S', -underline => 12,-command => \&save_values_as);
$root->g_bind('<Control-Alt-s>', \&save_values_as);
$file->add_separator();
$file->m_add_command(-label => "Quitter", -accelerator => 'Alt+F4', -underline => 0,-command => sub { $root->g_destroy() });
# The binding for Alt-F4 is automatically supplied by Windows and can't be
# overriden. It will destroy the window. We catch it as well as the menu entry
# using the following bind command.
# If we bind to $root, then the event triggers for all contained widget.
$root->g_bind('<Destroy>', [sub { autosave() if $_[0] eq '.' }, Tkx::Ev('%W')]);
my $options = $menu->new_menu;
$menu->m_add_cascade(-menu => $options, -label => "Options", -underline => 0);
my $automatic_duration = \$values{automatic_duration};
$options->m_add_checkbutton(-label => "Durée automatique", -variable => $automatic_duration, -onvalue => 1, -offvalue => 0, -accelerator => 'Ctrl+D');
$root->g_bind('<Control-d>', sub { $$automatic_duration = 1 - $$automatic_duration });
my $taxes = $menu->new_menu;
$menu->m_add_cascade(-menu => $taxes, -label => "Fiscalité", -underline => 1);
my $pinel_menu = $taxes->new_menu;
my @pinel_zone = ('Zone A bis', 'Zone A', 'Zone B1', 'Zone B2');
my $disable_pinel_zone = sub {
for my $z (@pinel_zone) {
$pinel_menu->m_entryconfigure($z, -state => 'disabled');
}
};
my $enable_pinel_zone = sub {
for my $z (@pinel_zone) {
$pinel_menu->m_entryconfigure($z, -state => 'normal');
}
};
$taxes->m_add_cascade(-menu => $pinel_menu, -label => "Loi Pinel", -underline => 4);
my $pinel_duration = \$values{pinel_duration};
# TODO: test if loading a file with pinel duration 0 results in having the zone disabled correctly.
$pinel_menu->add_radiobutton(-label => "Non", -variable => $pinel_duration, -value => 0, -command => sub { $disable_pinel_zone->(); calculate_all() });
$pinel_menu->add_radiobutton(-label => "6 ans", -variable => $pinel_duration, -value => 6, -command => sub { $enable_pinel_zone->(); calculate_all() });
$pinel_menu->add_radiobutton(-label => "9 ans", -variable => $pinel_duration, -value => 9, -command => sub { $enable_pinel_zone->(); calculate_all() });
$pinel_menu->add_radiobutton(-label => "12 ans", -variable => $pinel_duration, -value => 12, -command => sub { $enable_pinel_zone->(); calculate_all() });
$pinel_menu->add_separator();
my $pinel_zone = \$values{pinel_zone};
for my $i (0..$#pinel_zone) {
$pinel_menu->add_radiobutton(-label => $pinel_zone[$i], -variable => $pinel_zone,
-value => $i, -command => \&calculate_all);
}
}
# When Return is pressed, we first move the focus, to force a re-computation of
# the variables holding behind the currently edited field, if any.
$root->g_bind('<Return>', sub {
Tkx::focus('.');
calculate_all();
});
# We're done, we can show the UI.
$root->g_wm_deiconify();
}
# Update the values displayed in the core value table.
my @computed_values; # The output of compute_all()
sub update_displayed_mode {
my $current_mode = $modes_combobox->m_current();
$modes_combobox->m_selection('clear');
my $format = $modes_format[$current_mode];
for my $i (0..NUM_LOAN_DURATION-1) {
for my $j (0..NUM_LOAN_AMOUNT-1) {
$core_display_values[$i][$j] = $format->($computed_values[$i][$j][$current_mode]);
}
}
}
sub clear_displayed_table {
$display_table->m_delete($display_table->m_children(''));
}
# Update the values displayed in the by-year secondary table.
my (@last_update_to_table);
sub update_displayed_table {
# loan_duration, $loan_amount
my ($d, $a) = (@_, @last_update_to_table);
@last_update_to_table = (@_, @last_update_to_table);
return if @last_update_to_table == 0; # automatic call before any selection.
if ($values{automatic_duration} && $values{loan_durations}[$d] != $values{duration}) {
$values{duration} = $values{loan_durations}[$d];
refresh_all_fields();
return; # This method is called back by refresh_all_fields() through calculate_all().
}
clear_displayed_table();
# We don't configure the height of the table unconditionnally because doing so
# will slightly change the width of the widget for some weird reason. Forcing
# the width does not work well (and yield some ugly redraw), so we don't do it.
# All this is unused anyway now that we have verticall scrolling and a set size
( run in 0.741 second using v1.01-cache-2.11-cpan-e93a5daba3e )