Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10384261
window.vala
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Subscribers
None
window.vala
View Options
/*
Copyright © 2018 Write.as
This file is part of the Write.as GTK desktop app.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
public
class
WriteAs
.
MainWindow
:
Gtk
.
ApplicationWindow
{
private
Gtk
.
TextView
canvas
;
private
Gtk
.
HeaderBar
header
;
private
Gtk
.
ToggleButton
darkmode_button
;
private
static
string
data_dir
=
".writeas"
;
private
static
string
version
=
"1.0.1"
;
private
int
font_size
=
16
;
private
bool
dark_mode
=
false
;
private
string
font
=
"Lora, 'Palatino Linotype',"
+
"'Book Antiqua', 'New York', 'DejaVu serif', serif"
;
private
string
fontstyle
=
"serif"
;
private
bool
text_changed
=
false
;
private
bool
is_initializing
=
true
;
construct
{
header
=
new
Gtk
.
HeaderBar
();
header
.
title
=
"Write.as"
;
construct_toolbar
();
build_keyboard_shortcuts
();
var
scrolled
=
new
Gtk
.
ScrolledWindow
(
null
,
null
);
canvas
=
new
Gtk
.
SourceView
();
canvas
.
wrap_mode
=
Gtk
.
WrapMode
.
WORD_CHAR
;
scrolled
.
add
(
canvas
);
add
(
scrolled
);
size_allocate
.
connect
((
_
)
=>
{
adjust_text_style
();});
canvas
.
event_after
.
connect
((
evt
)
=>
{
// TODO This word count algorithm may be quite naive
// and could do improvement.
var
word_count
=
canvas
.
buffer
.
text
.
split
(
" "
).
length
;
header
.
subtitle
=
ngettext
(
"%i word"
,
"%i words"
,
word_count
).
printf
(
word_count
);
text_changed
=
true
;
});
Timeout
.
add_full
(
Priority
.
DEFAULT_IDLE
,
100
/*ms*/
,
()
=>
{
if
(
!
text_changed
)
return
Source
.
CONTINUE
;
var
text
=
canvas
.
buffer
.
text
;
// This happens sometimes for some reason, but it's difficult to debug.
if
(
text
==
""
)
return
Source
.
CONTINUE
;
try
{
draft_file
().
replace_contents
(
text
.
data
,
null
,
false
,
FileCreateFlags
.
PRIVATE
|
FileCreateFlags
.
REPLACE_DESTINATION
,
null
);
text_changed
=
false
;
}
catch
(
Error
err
)
{
/* We'll try again anyways. */
}
return
Source
.
CONTINUE
;
});
adjust_text_style
(
false
);
}
public
MainWindow
(
Gtk
.
Application
app
)
{
stdout
.
printf
(
"writeas-gtk v%s
\n
"
,
version
);
set_application
(
app
);
icon_name
=
"com.github.writeas.writeas-gtk"
;
init_folder
();
try
{
open_file
(
draft_file
());
}
catch
(
Error
err
)
{}
restore_styles
();
set_default_size
(
800
,
600
);
is_initializing
=
false
;
}
private
static
void
init_folder
()
{
var
home
=
File
.
new_for_path
(
get_data_dir
());
try
{
home
.
make_directory
();
}
catch
(
Error
e
)
{
stderr
.
printf
(
"Create data dir: %s
\n
"
,
e
.
message
);
}
}
private
static
string
get_data_dir
()
{
return
Environment
.
get_home_dir
()
+
"/"
+
data_dir
;
}
private
static
File
draft_file
()
{
var
home
=
File
.
new_for_path
(
get_data_dir
());
return
home
.
get_child
(
"draft.txt"
);
}
private
static
bool
supports_dark_theme
()
{
var
theme
=
Gtk
.
Settings
.
get_default
().
gtk_theme_name
;
foreach
(
var
datapath
in
Environment
.
get_system_data_dirs
())
{
var
path
=
File
.
new_for_path
(
Path
.
build_filename
(
datapath
,
"themes"
,
theme
));
if
(
path
.
get_child
(
"gtk-dark.css"
).
query_exists
())
return
true
;
try
{
var
enumerator
=
path
.
enumerate_children
(
"standard::*"
,
0
);
FileInfo
info
=
null
;
while
((
info
=
enumerator
.
next_file
())
!=
null
)
{
var
fullpath
=
path
.
get_child
(
info
.
get_name
()).
get_child
(
"gtk-dark.css"
);
if
(
fullpath
.
query_exists
())
return
true
;
}
}
catch
(
Error
err
)
{
/* Might be missing something, but no biggy. */
}
}
return
false
;
}
private
void
construct_toolbar
()
{
header
.
show_close_button
=
true
;
set_titlebar
(
header
);
var
publish_button
=
new
Gtk
.
Button
.
from_icon_name
(
"document-send"
,
Gtk
.
IconSize
.
SMALL_TOOLBAR
);
publish_button
.
tooltip_text
=
_
(
"Publish to Write.as on the web"
);
publish_button
.
clicked
.
connect
(()
=>
{
canvas
.
buffer
.
text
+=
"
\n\n
"
+
publish
();
canvas
.
grab_focus
();
});
header
.
pack_end
(
publish_button
);
darkmode_button
=
new
Gtk
.
ToggleButton
();
darkmode_button
.
tooltip_text
=
_
(
"Toggle dark theme"
);
// NOTE the fallback icon is a bit of a meaning stretch, but it works.
var
icon_theme
=
Gtk
.
IconTheme
.
get_default
();
darkmode_button
.
image
=
new
Gtk
.
Image
.
from_icon_name
(
icon_theme
.
has_icon
(
"writeas-bright-dark"
)
?
"writeas-bright-dark"
:
"weather-clear-night"
,
Gtk
.
IconSize
.
SMALL_TOOLBAR
);
darkmode_button
.
draw_indicator
=
false
;
var
settings
=
Gtk
.
Settings
.
get_default
();
darkmode_button
.
toggled
.
connect
(()
=>
{
settings
.
gtk_application_prefer_dark_theme
=
darkmode_button
.
active
;
dark_mode
=
darkmode_button
.
active
;
if
(
!
is_initializing
)
theme_save
();
canvas
.
grab_focus
();
});
if
(
supports_dark_theme
())
header
.
pack_end
(
darkmode_button
);
var
fonts
=
new
Gtk
.
MenuButton
();
fonts
.
tooltip_text
=
_
(
"Change document font"
);
fonts
.
image
=
new
Gtk
.
Image
.
from_icon_name
(
"font-x-generic"
,
Gtk
.
IconSize
.
SMALL_TOOLBAR
);
fonts
.
popup
=
new
Gtk
.
Menu
();
header
.
pack_start
(
fonts
);
build_fontoption
(
fonts
.
popup
,
_
(
"Serif"
),
"serif"
,
font
);
build_fontoption
(
fonts
.
popup
,
_
(
"Sans-serif"
),
"sans"
,
"'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif"
);
build_fontoption
(
fonts
.
popup
,
_
(
"Monospace"
),
"wrap"
,
"Hack, consolas,"
+
"Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace"
);
fonts
.
popup
.
show_all
();
}
private
unowned
SList
<
Gtk
.
RadioMenuItem
>?
font_options
=
null
;
private
void
build_fontoption
(
Gtk
.
Menu
menu
,
string
label
,
string
fontstyle
,
string
families
)
{
var
option
=
new
Gtk
.
RadioMenuItem
.
with_label
(
font_options
,
label
);
font_options
=
option
.
get_group
();
option
.
activate
.
connect
(()
=>
{
this
.
font
=
families
;
this
.
fontstyle
=
fontstyle
;
adjust_text_style
(
!
is_initializing
);
canvas
.
grab_focus
();
});
var
styles
=
option
.
get_style_context
();
var
provider
=
new
Gtk
.
CssProvider
();
try
{
provider
.
load_from_data
(
"* {font-family: %s;}"
.
printf
(
families
));
styles
.
add_provider
(
provider
,
Gtk
.
STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
catch
(
Error
e
)
{
warning
(
e
.
message
);
}
menu
.
add
(
option
);
}
public
override
void
grab_focus
()
{
canvas
.
grab_focus
();
}
private
KeyFile
theme
=
new
KeyFile
();
private
void
restore_styles
()
{
try
{
loaded_theme
=
true
;
theme
.
load_from_file
(
get_data_dir
()
+
"/prefs.ini"
,
KeyFileFlags
.
NONE
);
dark_mode
=
theme
.
get_boolean
(
"Theme"
,
"darkmode"
);
darkmode_button
.
set_active
(
dark_mode
);
Gtk
.
Settings
.
get_default
().
gtk_application_prefer_dark_theme
=
dark_mode
;
font_size
=
theme
.
get_integer
(
"Theme"
,
"fontsize"
);
font
=
theme
.
get_string
(
"Post"
,
"font"
);
fontstyle
=
theme
.
get_string
(
"Post"
,
"fontstyle"
);
adjust_text_style
(
false
);
}
catch
(
Error
err
)
{
/* No biggy... */
}
}
private
Gtk
.
CssProvider
cur_styles
=
null
;
// So the theme isn't read before it's saved.
private
bool
loaded_theme
=
false
;
private
void
adjust_text_style
(
bool
save_theme
=
true
)
{
try
{
if
(
cur_styles
!=
null
)
Gtk
.
StyleContext
.
remove_provider_for_screen
(
Gdk
.
Screen
.
get_default
(),
cur_styles
);
var
padding
=
canvas
.
get_allocated_width
()
*
0.10
;
var
css
=
(
"textview {font-family: %s; font-size: %dpx; padding: 20px;"
+
" padding-left: %ipx; padding-right: %ipx;"
+
" caret-color: #5ac4ee;}"
).
printf
(
font
,
font_size
,
(
int
)
padding
,
(
int
)
padding
);
cur_styles
=
new
Gtk
.
CssProvider
();
cur_styles
.
load_from_data
(
css
);
Gtk
.
StyleContext
.
add_provider_for_screen
(
Gdk
.
Screen
.
get_default
(),
cur_styles
,
Gtk
.
STYLE_PROVIDER_PRIORITY_APPLICATION
);
if
(
save_theme
)
theme_save
();
}
catch
(
Error
e
)
{
warning
(
e
.
message
);
}
}
private
void
theme_save
()
{
if
(
!
loaded_theme
)
return
;
theme
.
set_boolean
(
"Theme"
,
"darkmode"
,
dark_mode
);
theme
.
set_integer
(
"Theme"
,
"fontsize"
,
font_size
);
theme
.
set_string
(
"Post"
,
"font"
,
font
);
theme
.
set_string
(
"Post"
,
"fontstyle"
,
fontstyle
);
try
{
theme
.
save_to_file
(
get_data_dir
()
+
"/prefs.ini"
);
}
catch
(
FileError
err
)
{
/* Oh well. */
}
}
private
string
publish
()
{
try
{
if
(
text_changed
)
{;
draft_file
().
replace_contents
(
canvas
.
buffer
.
text
.
data
,
null
,
false
,
FileCreateFlags
.
PRIVATE
|
FileCreateFlags
.
REPLACE_DESTINATION
,
null
);
text_changed
=
false
;
}
var
cmd
=
"sh -c 'cat ~/"
+
data_dir
+
"/draft.txt | writeas --md --font %s --user-agent
\"
writeas-gtk v"
+
version
+
"
\"
'"
;
cmd
=
cmd
.
printf
(
fontstyle
);
string
stdout
,
stderr
;
int
status
;
Process
.
spawn_command_line_sync
(
cmd
,
out
stdout
,
out
stderr
,
out
status
);
// Open it in the browser
if
(
status
==
0
)
{
var
browser
=
AppInfo
.
get_default_for_uri_scheme
(
"https"
);
var
urls
=
new
List
<
string
>
();
urls
.
append
(
stdout
.
strip
());
browser
.
launch_uris
(
urls
,
null
);
}
return
stderr
.
strip
();
}
catch
(
Error
err
)
{
return
err
.
message
;
}
}
/* --- */
private
void
build_keyboard_shortcuts
()
{
/* These operations are not exposed to the UI as buttons,
as most people are very familiar with them and they are not the
focus of this app. */
var
accels
=
new
Gtk
.
AccelGroup
();
// App operations
accels
.
connect
(
Gdk
.
Key
.
W
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
quit
());
accels
.
connect
(
Gdk
.
Key
.
Q
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
quit
());
// File operations
accels
.
connect
(
Gdk
.
Key
.
S
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
save_as
());
accels
.
connect
(
Gdk
.
Key
.
S
,
Gdk
.
ModifierType
.
CONTROL_MASK
|
Gdk
.
ModifierType
.
SHIFT_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
save_as
());
// Adjust text size
accels
.
connect
(
Gdk
.
Key
.
minus
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
{
if
(
font_size
<
3
)
{
return
false
;
}
if
(
font_size
<=
10
)
{
font_size
-=
1
;
}
else
{
font_size
-=
2
;
}
adjust_text_style
(
true
);
return
true
;
});
accels
.
connect
(
Gdk
.
Key
.
equal
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
{
if
(
font_size
<
10
)
{
font_size
+=
1
;
}
else
{
font_size
+=
2
;
}
adjust_text_style
(
true
);
return
true
;
});
// Toggle theme with Ctrl+T
accels
.
connect
(
Gdk
.
Key
.
T
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
{
darkmode_button
.
set_active
(
!
darkmode_button
.
get_active
());
return
true
;
});
// Publish with Ctrl+Enter
accels
.
connect
(
Gdk
.
Key
.
Return
,
Gdk
.
ModifierType
.
CONTROL_MASK
,
Gtk
.
AccelFlags
.
VISIBLE
|
Gtk
.
AccelFlags
.
LOCKED
,
(
g
,
a
,
k
,
m
)
=>
{
canvas
.
buffer
.
text
+=
"
\n\n
"
+
publish
();
return
true
;
});
add_accel_group
(
accels
);
}
private
bool
save_as
()
{
try
{
var
file
=
prompt_file
(
Gtk
.
FileChooserAction
.
SAVE
,
_
(
"Save as"
));
file
.
replace_contents
(
canvas
.
buffer
.
text
.
data
,
null
,
false
,
FileCreateFlags
.
PRIVATE
|
FileCreateFlags
.
REPLACE_DESTINATION
,
null
);
}
catch
(
Error
e
)
{
// It's fine...
}
return
true
;
}
private
File
prompt_file
(
Gtk
.
FileChooserAction
mode
,
string
action
)
throws
UserCancellable
{
var
file_chooser
=
new
Gtk
.
FileChooserDialog
(
action
,
this
,
mode
,
_
(
"Cancel"
),
Gtk
.
ResponseType
.
CANCEL
,
action
,
Gtk
.
ResponseType
.
ACCEPT
);
file_chooser
.
select_multiple
=
false
;
var
filter
=
new
Gtk
.
FileFilter
();
filter
.
add_mime_type
(
"text/plain"
);
file_chooser
.
set_filter
(
filter
);
var
resp
=
file_chooser
.
run
();
file_chooser
.
close
();
if
(
resp
==
Gtk
.
ResponseType
.
ACCEPT
)
{
return
file_chooser
.
get_file
();
}
else
{
throw
new
UserCancellable
.
USER_CANCELLED
(
"FileChooserDialog"
);
}
}
public
void
open_file
(
File
file
)
throws
Error
{
uint8
[]
text
;
file
.
load_contents
(
null
,
out
text
,
null
);
canvas
.
buffer
.
text
=
(
string
)
text
;
}
private
bool
quit
()
{
this
.
close
();
return
true
;
}
}
errordomain
WriteAs
.
UserCancellable
{
USER_CANCELLED
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 23, 9:07 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3104493
Attached To
rWAGTK writeas-gtk
Event Timeline
Log In to Comment