Write.as GTK desktop app https://write.as/apps/desktop
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

243 linhas
8.7 KiB

  1. public class WriteAs.MainWindow : Gtk.ApplicationWindow {
  2. private Gtk.TextView canvas;
  3. private bool dark_mode = false;
  4. private string font = "Lora, 'Palatino Linotype',"
  5. + "'Book Antiqua', 'New York', 'DejaVu serif', serif";
  6. private string fontstyle = "serif";
  7. private bool text_changed = false;
  8. construct {
  9. construct_toolbar();
  10. build_keyboard_shortcuts();
  11. var scrolled = new Gtk.ScrolledWindow(null, null);
  12. canvas = new Gtk.TextView();
  13. canvas.wrap_mode = Gtk.WrapMode.WORD_CHAR;
  14. scrolled.add(canvas);
  15. add(scrolled);
  16. canvas.event_after.connect((evt) => {
  17. // TODO This word count algorithm may be quite naive
  18. // and could do improvement.
  19. var word_count = canvas.buffer.text.split(" ").length;
  20. title = ngettext("%i word","%i words",word_count).printf(word_count);
  21. text_changed = true;
  22. });
  23. Timeout.add_full(Priority.DEFAULT_IDLE, 100/*ms*/, () => {
  24. if (!text_changed) return Source.CONTINUE;
  25. try {
  26. draft_file().replace_contents(canvas.buffer.text.data, null, false,
  27. FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION,
  28. null);
  29. text_changed = false;
  30. } catch (Error err) {/* We'll try again anyways. */}
  31. return Source.CONTINUE;
  32. });
  33. adjust_text_style();
  34. }
  35. public MainWindow(Gtk.Application app) {
  36. set_application(app);
  37. try {
  38. open_file(draft_file());
  39. } catch (Error err) {/* It's fine... */}
  40. set_default_size(800, 600);
  41. }
  42. private static File draft_file() {
  43. var home = File.new_for_path(Environment.get_home_dir());
  44. return home.get_child(".writeas-draft.txt");
  45. }
  46. private void construct_toolbar() {
  47. var header = new Gtk.HeaderBar();
  48. header.show_close_button = true;
  49. set_titlebar(header);
  50. var publish_button = new Gtk.Button.from_icon_name("document-send",
  51. Gtk.IconSize.SMALL_TOOLBAR);
  52. publish_button.clicked.connect(() => {
  53. title = _("Publishing post…");
  54. canvas.sensitive = false;
  55. publish.begin((obj, res) => {
  56. canvas.buffer.text += "\n\n" + publish.end(res);
  57. canvas.sensitive = true;
  58. });
  59. });
  60. header.pack_end(publish_button);
  61. var darkmode_button = new Gtk.ToggleButton();
  62. darkmode_button.tooltip_text = _("Toggle dark theme");
  63. // NOTE the fallback icon is a bit of a meaning stretch, but it works.
  64. var icon_theme = Gtk.IconTheme.get_default();
  65. darkmode_button.image = new Gtk.Image.from_icon_name(
  66. icon_theme.has_icon("writeas-bright-dark") ?
  67. "writeas-bright-dark" : "weather-clear-night",
  68. Gtk.IconSize.SMALL_TOOLBAR);
  69. darkmode_button.draw_indicator = false;
  70. var settings = Gtk.Settings.get_default();
  71. darkmode_button.toggled.connect(() => {
  72. settings.gtk_application_prefer_dark_theme = darkmode_button.active;
  73. dark_mode = darkmode_button.active;
  74. adjust_text_style();
  75. });
  76. header.pack_end(darkmode_button);
  77. var fonts = new Gtk.MenuButton();
  78. fonts.tooltip_text = _("Change document font");
  79. fonts.image = new Gtk.Image.from_icon_name("font-x-generic", Gtk.IconSize.SMALL_TOOLBAR);
  80. fonts.popup = new Gtk.Menu();
  81. header.pack_start(fonts);
  82. build_fontoption(fonts.popup, _("Serif"), "serif", font);
  83. build_fontoption(fonts.popup, _("Sans-serif"), "sans",
  84. "'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif");
  85. build_fontoption(fonts.popup, _("Monospace"), "wrap", "Hack, consolas," +
  86. "Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace");
  87. fonts.popup.show_all();
  88. }
  89. private void build_fontoption(Gtk.Menu menu,
  90. string label, string fontstyle, string families) {
  91. var option = new Gtk.MenuItem.with_label(label);
  92. option.activate.connect(() => {
  93. this.font = families;
  94. this.fontstyle = fontstyle;
  95. adjust_text_style();
  96. });
  97. var styles = option.get_style_context();
  98. var provider = new Gtk.CssProvider();
  99. try {
  100. provider.load_from_data("* {font: %s;}".printf(families));
  101. styles.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
  102. } catch (Error e) {
  103. warning(e.message);
  104. }
  105. menu.add(option);
  106. }
  107. private Gtk.CssProvider cur_styles = null;
  108. private void adjust_text_style() {
  109. try {
  110. var styles = canvas.get_style_context();
  111. if (cur_styles != null) styles.remove_provider(cur_styles);
  112. var css = "* {font: %s; padding: 20px;}".printf(font);
  113. if (dark_mode) {
  114. // Try to detect whether the system provided a better dark mode.
  115. var text_color = styles.get_color(Gtk.StateFlags.ACTIVE);
  116. double h, s, v;
  117. Gtk.rgb_to_hsv(text_color.red, text_color.green, text_color.blue,
  118. out h, out s, out v);
  119. if (v < 0.5) css += "* {background: black; color: white;}";
  120. }
  121. cur_styles = new Gtk.CssProvider();
  122. cur_styles.load_from_data(css);
  123. styles.add_provider(cur_styles, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
  124. } catch (Error e) {
  125. warning(e.message);
  126. }
  127. }
  128. private async string publish() {
  129. try {
  130. if (text_changed) {;
  131. draft_file().replace_contents(canvas.buffer.text.data, null, false,
  132. FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION,
  133. null);
  134. text_changed = false;
  135. }
  136. var cmd = "sh -c 'cat ~/.writeas-draft.txt | writeas'";
  137. string stdout, stderr;
  138. int status;
  139. Process.spawn_command_line_sync(cmd,
  140. out stdout, out stderr, out status);
  141. // Open it in the browser
  142. var browser = AppInfo.get_default_for_uri_scheme("https");
  143. var urls = new List<string>();
  144. urls.append(stdout.strip());
  145. browser.launch_uris(urls, null);
  146. return stderr.strip();
  147. } catch (Error err) {
  148. return err.message;
  149. }
  150. }
  151. /* --- */
  152. private void build_keyboard_shortcuts() {
  153. /* These operations are not exposed to the UI as buttons,
  154. as most people are very familiar with them and they are not the
  155. focus of this app. */
  156. var accels = new Gtk.AccelGroup();
  157. accels.connect(Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK,
  158. Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED,
  159. (g,a,k,m) => save_as());
  160. accels.connect(Gdk.Key.S,
  161. Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK,
  162. Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED,
  163. (g,a,k,m) => save_as());
  164. accels.connect(Gdk.Key.O, Gdk.ModifierType.CONTROL_MASK,
  165. Gtk.AccelFlags.VISIBLE | Gtk.AccelFlags.LOCKED, (g, a, k, m) => {
  166. try {
  167. open_file(prompt_file(Gtk.FileChooserAction.OPEN, _("_Open")));
  168. } catch (Error e) {
  169. // It's fine...
  170. }
  171. return true;
  172. });
  173. add_accel_group(accels);
  174. }
  175. private bool save_as() {
  176. try {
  177. var file = prompt_file(Gtk.FileChooserAction.SAVE, _("_Save as"));
  178. file.replace_contents(canvas.buffer.text.data, null, false,
  179. FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION,
  180. null);
  181. } catch (Error e) {
  182. // It's fine...
  183. }
  184. return true;
  185. }
  186. private File prompt_file(Gtk.FileChooserAction mode, string action)
  187. throws UserCancellable {
  188. var file_chooser = new Gtk.FileChooserDialog(action, this, mode,
  189. _("_Cancel"), Gtk.ResponseType.CANCEL,
  190. action, Gtk.ResponseType.ACCEPT);
  191. file_chooser.select_multiple = false;
  192. var filter = new Gtk.FileFilter();
  193. filter.add_mime_type("text/plain");
  194. file_chooser.set_filter(filter);
  195. var resp = file_chooser.run();
  196. file_chooser.close();
  197. if (resp == Gtk.ResponseType.ACCEPT) return file_chooser.get_file();
  198. else throw new UserCancellable.USER_CANCELLED("FileChooserDialog");
  199. }
  200. public void open_file(File file) throws Error {
  201. uint8[] text;
  202. file.load_contents(null, out text, null);
  203. canvas.buffer.text = (string) text;
  204. }
  205. }
  206. errordomain WriteAs.UserCancellable {USER_CANCELLED}