themactep-grumblr 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +13 -0
- data/README +42 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/bin/grumblr +27 -0
- data/data/grumblr.desktop +9 -0
- data/grumblr.gemspec +66 -0
- data/images/audio.bmp +0 -0
- data/images/chat.bmp +0 -0
- data/images/grumblr.svg +110 -0
- data/images/link.bmp +0 -0
- data/images/photo.bmp +0 -0
- data/images/quote.bmp +0 -0
- data/images/spinner.gif +0 -0
- data/images/text.bmp +0 -0
- data/images/video.bmp +0 -0
- data/lib/grumblr/core.rb +38 -0
- data/lib/grumblr/ui.rb +598 -0
- data/lib/ppds/class_factory.rb +17 -0
- data/lib/ppds/config.rb +47 -0
- data/lib/ppds/tumblr.rb +85 -0
- metadata +95 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
nbproject
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
== Grumblr
|
2
|
+
|
3
|
+
Copyright (c) 2009, Paul Philippov
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
10
|
+
* Neither the name of the Paul Philippov nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
13
|
+
|
data/README
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
== Grumblr ==
|
2
|
+
|
3
|
+
Grumblr is a tool to post messages to your Tumblr blog from GNOME.
|
4
|
+
|
5
|
+
=== Licence ===
|
6
|
+
|
7
|
+
New BSD License
|
8
|
+
|
9
|
+
|
10
|
+
=== Requirements: ===
|
11
|
+
|
12
|
+
* Ruby
|
13
|
+
http://www.ruby-lang.org/
|
14
|
+
|
15
|
+
* GTK+
|
16
|
+
http://www.mozilla.org/
|
17
|
+
|
18
|
+
* Ruby Gtk2 bindings and Ruby/GTK bindings for Mozilla
|
19
|
+
from Ruby-GNOME2 Project
|
20
|
+
http://ruby-gnome2.sourceforge.jp/
|
21
|
+
|
22
|
+
* LibXml Ruby
|
23
|
+
http://libxml.rubyforge.org/
|
24
|
+
|
25
|
+
* REST Client
|
26
|
+
http://github.com/adamwiggins/rest-client/
|
27
|
+
|
28
|
+
|
29
|
+
=== Installation ===
|
30
|
+
|
31
|
+
$ sudo gem source -a http://gems.github.com/
|
32
|
+
$ sudo gem install themactep-grumblr
|
33
|
+
|
34
|
+
|
35
|
+
=== Homepage ===
|
36
|
+
|
37
|
+
http://github.com/themactep/grumblr
|
38
|
+
|
39
|
+
|
40
|
+
=== Authors ===
|
41
|
+
|
42
|
+
Paul Philippov <themactep@gmail.com>
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gemspec|
|
8
|
+
gemspec.name = "grumblr"
|
9
|
+
gemspec.summary = "Tumblr companion for GNOME"
|
10
|
+
gemspec.description = "Grumblr is a message poster to Tumblr blogs from GNOME."
|
11
|
+
gemspec.email = "themactep@gmail.com"
|
12
|
+
gemspec.homepage = "http://github.com/themactep/grumblr"
|
13
|
+
gemspec.authors = ["Paul Philippov"]
|
14
|
+
gemspec.add_dependency "libxml-ruby"
|
15
|
+
gemspec.add_dependency "rest-client"
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/bin/grumblr
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# $Id: grumblr.rb 2009/08/09 21:48:53 EET paul Exp $
|
3
|
+
#
|
4
|
+
=begin
|
5
|
+
Grumblr - Tumblr companion for GNOME.
|
6
|
+
|
7
|
+
Copyright (c) 2009, Paul Philippov <paul@ppds.ws>
|
8
|
+
|
9
|
+
This software is released under the BSD License.
|
10
|
+
http://creativecommons.org/licenses/BSD/
|
11
|
+
=end
|
12
|
+
|
13
|
+
app_file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
14
|
+
APP_ROOT = File.expand_path(File.dirname(app_file))
|
15
|
+
Dir.glob(APP_ROOT + '/../**/').reverse.each { |d| $:.unshift d }
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'rubygems'
|
19
|
+
rescue LoadError
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'grumblr/core'
|
24
|
+
|
25
|
+
Grumblr::Core.new
|
26
|
+
Thread.new { Grumblr::StatusIcon.new }
|
27
|
+
$app.main
|
data/grumblr.gemspec
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{grumblr}
|
8
|
+
s.version = "2.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Paul Philippov"]
|
12
|
+
s.date = %q{2009-08-22}
|
13
|
+
s.default_executable = %q{grumblr}
|
14
|
+
s.description = %q{Grumblr is a message poster to Tumblr blogs from GNOME.}
|
15
|
+
s.email = %q{themactep@gmail.com}
|
16
|
+
s.executables = ["grumblr"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"README",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/grumblr",
|
28
|
+
"data/grumblr.desktop",
|
29
|
+
"grumblr.gemspec",
|
30
|
+
"images/audio.bmp",
|
31
|
+
"images/chat.bmp",
|
32
|
+
"images/grumblr.svg",
|
33
|
+
"images/link.bmp",
|
34
|
+
"images/photo.bmp",
|
35
|
+
"images/quote.bmp",
|
36
|
+
"images/spinner.gif",
|
37
|
+
"images/text.bmp",
|
38
|
+
"images/video.bmp",
|
39
|
+
"lib/grumblr/core.rb",
|
40
|
+
"lib/grumblr/ui.rb",
|
41
|
+
"lib/ppds/class_factory.rb",
|
42
|
+
"lib/ppds/config.rb",
|
43
|
+
"lib/ppds/tumblr.rb"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/themactep/grumblr}
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.3.5}
|
49
|
+
s.summary = %q{Tumblr companion for GNOME}
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
56
|
+
s.add_runtime_dependency(%q<libxml-ruby>, [">= 0"])
|
57
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 0"])
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<libxml-ruby>, [">= 0"])
|
60
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
61
|
+
end
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<libxml-ruby>, [">= 0"])
|
64
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
65
|
+
end
|
66
|
+
end
|
data/images/audio.bmp
ADDED
Binary file
|
data/images/chat.bmp
ADDED
Binary file
|
data/images/grumblr.svg
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
3
|
+
|
4
|
+
<svg
|
5
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
6
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
7
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
8
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
10
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
12
|
+
width="512"
|
13
|
+
height="512"
|
14
|
+
id="svg6643"
|
15
|
+
version="1.1"
|
16
|
+
inkscape:version="0.47pre1 r21720"
|
17
|
+
sodipodi:docname="grumblr.svg"
|
18
|
+
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
19
|
+
<defs
|
20
|
+
id="defs6645">
|
21
|
+
<inkscape:perspective
|
22
|
+
sodipodi:type="inkscape:persp3d"
|
23
|
+
inkscape:vp_x="0 : 32 : 1"
|
24
|
+
inkscape:vp_y="0 : 1000 : 0"
|
25
|
+
inkscape:vp_z="64 : 32 : 1"
|
26
|
+
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
27
|
+
id="perspective6651" />
|
28
|
+
<inkscape:perspective
|
29
|
+
id="perspective6631"
|
30
|
+
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
31
|
+
inkscape:vp_z="1 : 0.5 : 1"
|
32
|
+
inkscape:vp_y="0 : 1000 : 0"
|
33
|
+
inkscape:vp_x="0 : 0.5 : 1"
|
34
|
+
sodipodi:type="inkscape:persp3d" />
|
35
|
+
</defs>
|
36
|
+
<sodipodi:namedview
|
37
|
+
id="base"
|
38
|
+
pagecolor="#ffffff"
|
39
|
+
bordercolor="#666666"
|
40
|
+
borderopacity="1.0"
|
41
|
+
inkscape:pageopacity="0.0"
|
42
|
+
inkscape:pageshadow="2"
|
43
|
+
inkscape:zoom="0.97227183"
|
44
|
+
inkscape:cx="255.53213"
|
45
|
+
inkscape:cy="255.7224"
|
46
|
+
inkscape:current-layer="layer1"
|
47
|
+
showgrid="false"
|
48
|
+
inkscape:document-units="px"
|
49
|
+
inkscape:grid-bbox="true"
|
50
|
+
inkscape:window-width="1440"
|
51
|
+
inkscape:window-height="825"
|
52
|
+
inkscape:window-x="0"
|
53
|
+
inkscape:window-y="25"
|
54
|
+
inkscape:window-maximized="1">
|
55
|
+
<inkscape:grid
|
56
|
+
type="xygrid"
|
57
|
+
id="grid2823"
|
58
|
+
empspacing="5"
|
59
|
+
visible="true"
|
60
|
+
enabled="true"
|
61
|
+
snapvisiblegridlinesonly="true" />
|
62
|
+
</sodipodi:namedview>
|
63
|
+
<metadata
|
64
|
+
id="metadata6648">
|
65
|
+
<rdf:RDF>
|
66
|
+
<cc:Work
|
67
|
+
rdf:about="">
|
68
|
+
<dc:format>image/svg+xml</dc:format>
|
69
|
+
<dc:type
|
70
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
71
|
+
<dc:title />
|
72
|
+
</cc:Work>
|
73
|
+
</rdf:RDF>
|
74
|
+
</metadata>
|
75
|
+
<g
|
76
|
+
id="layer1"
|
77
|
+
inkscape:label="Layer 1"
|
78
|
+
inkscape:groupmode="layer"
|
79
|
+
transform="translate(0,448)">
|
80
|
+
<g
|
81
|
+
id="g2820"
|
82
|
+
transform="matrix(0.99800965,0,0,0.99800965,0.1055055,-2.036518)">
|
83
|
+
<path
|
84
|
+
id="path3605"
|
85
|
+
d="m 256.54485,-440.84091 c -138.2071,0 -250.6386,112.43141 -250.6386,250.6386 0,138.207112 112.4315,250.35856 250.6386,250.35856 138.2071,0 250.35856,-112.151448 250.35856,-250.35856 0,-138.20719 -112.15146,-250.6386 -250.35856,-250.6386 z"
|
86
|
+
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" />
|
87
|
+
<path
|
88
|
+
id="path8-7-6"
|
89
|
+
d="m 256.53616,-410.78108 c -121.94187,0 -220.57008,98.62809 -220.57008,220.57003 0,121.941854 98.62821,220.30747 220.57008,220.30747 86.47283,0 154.49575,-36.9921661 190.59612,-109.260948 -58.46055,-11.134087 -137.96685,-4.693712 -227.88298,14.376441 -2.13629,0.856345 -4.64884,0.716097 -6.67656,-0.372685 -2.02771,-1.088783 -5.0478,-4.419462 -5.0478,-14.003756 0,-9.584293 1.16361,-10.670758 2.59282,-12.474768 1.42921,-1.804009 3.67982,-2.929738 5.98054,-2.991394 46.4443,-9.85029 91.86095,-16.03896 133.39239,-18.11825 41.5314,-2.0793 79.20574,-0.24608 110.28502,6.03941 0.17498,-0.006 0.35015,-0.006 0.52514,0 10.52606,-25.756 16.54273,-53.89965 16.54273,-83.50152 0,-121.94194 -98.3656,-220.57003 -220.30746,-220.57003 l 4e-5,0 z"
|
90
|
+
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#ffa500;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans"
|
91
|
+
sodipodi:nodetypes="cssccssscsccscc" />
|
92
|
+
<path
|
93
|
+
id="path10-5"
|
94
|
+
style="fill:#ffffff;stroke:none"
|
95
|
+
d="m 363.80317,-207.45687 c 0,33.53488 -24.97949,60.7203 -55.79328,60.7203 -30.81378,0 -55.79331,-27.18542 -55.79331,-60.7203 0,-33.5349 24.97953,-60.72031 55.79331,-60.72031 30.81379,0 55.79328,27.18541 55.79328,60.72031 z" />
|
96
|
+
<path
|
97
|
+
id="path14-3"
|
98
|
+
style="fill:#ffffff;stroke:none"
|
99
|
+
d="m 467.85013,-212.31538 c 0,29.39149 -20.48895,53.21798 -45.76332,53.21798 -25.27436,0 -45.76332,-23.82649 -45.76332,-53.21798 0,-29.39149 20.48896,-53.21804 45.76332,-53.21804 25.27437,0 45.76332,23.82655 45.76332,53.21804 z" />
|
100
|
+
<path
|
101
|
+
id="path16-3"
|
102
|
+
style="fill:#000000"
|
103
|
+
d="m 316.30525,-183.79454 c 0,9.97261 -8.32793,18.05695 -18.60095,18.05695 -10.273,0 -18.60092,-8.08434 -18.60092,-18.05695 0,-9.97252 8.32792,-18.05686 18.60092,-18.05686 10.27302,0 18.60095,8.08434 18.60095,18.05686 z" />
|
104
|
+
<path
|
105
|
+
id="path18-1"
|
106
|
+
style="fill:#000000"
|
107
|
+
d="m 421.37793,-193.1727 c 0,8.1655 -7.05731,14.78492 -15.76289,14.78492 -8.70565,0 -15.76292,-6.61942 -15.76292,-14.78492 0,-8.1655 7.05727,-14.78489 15.76292,-14.78489 8.70558,0 15.76289,6.61939 15.76289,14.78489 z" />
|
108
|
+
</g>
|
109
|
+
</g>
|
110
|
+
</svg>
|
data/images/link.bmp
ADDED
Binary file
|
data/images/photo.bmp
ADDED
Binary file
|
data/images/quote.bmp
ADDED
Binary file
|
data/images/spinner.gif
ADDED
Binary file
|
data/images/text.bmp
ADDED
Binary file
|
data/images/video.bmp
ADDED
Binary file
|
data/lib/grumblr/core.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ppds/config'
|
2
|
+
require 'ppds/tumblr'
|
3
|
+
require 'grumblr/ui'
|
4
|
+
require 'gtk2'
|
5
|
+
|
6
|
+
module Grumblr
|
7
|
+
IMAGE_ROOT = File.expand_path(File.join(APP_ROOT, '..', 'images'))
|
8
|
+
|
9
|
+
class Core
|
10
|
+
attr_accessor :blog
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
$app = self
|
14
|
+
|
15
|
+
$cfg = Ppds::Config.new 'grumblr'
|
16
|
+
|
17
|
+
$gui = Grumblr::UI.new
|
18
|
+
$gui.show_all
|
19
|
+
|
20
|
+
$api = Ppds::Tumblr.new
|
21
|
+
if $api.authenticate($cfg.get(:email), $cfg.get(:password))
|
22
|
+
$gui.add Grumblr::Dashboard.new
|
23
|
+
else
|
24
|
+
$gui.add Grumblr::SettingsFrame.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def main
|
29
|
+
Gtk::main
|
30
|
+
end
|
31
|
+
|
32
|
+
def quit
|
33
|
+
Gtk::main_quit
|
34
|
+
ensure
|
35
|
+
$cfg.save
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/grumblr/ui.rb
ADDED
@@ -0,0 +1,598 @@
|
|
1
|
+
require 'gtk2'
|
2
|
+
|
3
|
+
module Grumblr
|
4
|
+
class UI < Gtk::Window
|
5
|
+
|
6
|
+
attr_accessor :logo
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super Gtk::Window::TOPLEVEL
|
10
|
+
|
11
|
+
filename = File.join(Grumblr::IMAGE_ROOT, 'grumblr.svg')
|
12
|
+
self.logo = Gdk::Pixbuf.new filename, 128, 128
|
13
|
+
|
14
|
+
self.set_size_request 480, 360
|
15
|
+
self.set_allow_shrink false
|
16
|
+
self.set_title 'Grumblr'
|
17
|
+
|
18
|
+
self.set_icon self.logo
|
19
|
+
self.set_default_width $cfg.get(:window_width).to_i
|
20
|
+
self.set_default_height $cfg.get(:window_height).to_i
|
21
|
+
self.move $cfg.get(:window_x_pos).to_i, $cfg.get(:window_y_pos).to_i
|
22
|
+
self.signal_connect(:destroy) { quit }
|
23
|
+
self.signal_connect(:delete_event) { minimize }
|
24
|
+
self.signal_connect(:check_resize) do |widget|
|
25
|
+
x, y = widget.position
|
26
|
+
w, h = widget.size
|
27
|
+
$cfg.set :window_x_pos, x
|
28
|
+
$cfg.set :window_y_pos, y
|
29
|
+
$cfg.set :window_width, w
|
30
|
+
$cfg.set :window_height, h
|
31
|
+
end
|
32
|
+
signal_connect(:window_state_event) do |widget, e|
|
33
|
+
case e.event_type
|
34
|
+
when Gdk::Event::WINDOW_STATE
|
35
|
+
minimize if e.changed_mask.iconified? and e.new_window_state.iconified?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
show
|
39
|
+
end
|
40
|
+
|
41
|
+
def minimize
|
42
|
+
self.hide
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Dashboard < Gtk::VBox
|
47
|
+
def initialize
|
48
|
+
super false, 4
|
49
|
+
|
50
|
+
##
|
51
|
+
### Statusbar
|
52
|
+
##
|
53
|
+
@statusbar = Gtk::Statusbar.new
|
54
|
+
|
55
|
+
##
|
56
|
+
### Notebook
|
57
|
+
##
|
58
|
+
@notebook = Gtk::Notebook.new
|
59
|
+
@notebook.set_homogeneous true
|
60
|
+
@notebook.set_tab_pos Gtk::POS_LEFT
|
61
|
+
|
62
|
+
#
|
63
|
+
# Text page
|
64
|
+
#
|
65
|
+
@text_title = Gtk::Entry.new
|
66
|
+
|
67
|
+
@format = Gtk::CheckButton.new '_markdown'
|
68
|
+
@format.set_active $cfg.get(:format_markdown)
|
69
|
+
@format.signal_connect(:toggled) do |widget|
|
70
|
+
$cfg.set :format_markdown, widget.active?
|
71
|
+
end
|
72
|
+
|
73
|
+
label = Gtk::Label.new 'Body'
|
74
|
+
|
75
|
+
box = Gtk::HBox.new false, 8
|
76
|
+
box.pack_start label, false
|
77
|
+
box.pack_start @format, false
|
78
|
+
|
79
|
+
page = Gtk::VBox.new false, 4
|
80
|
+
page.set_border_width 8
|
81
|
+
page.pack_with_label 'Title (optional)', @text_title
|
82
|
+
page.pack_start box, false
|
83
|
+
page.pack_start multiline_entry(:text_body), true
|
84
|
+
|
85
|
+
@notebook.add_page_with_tab page, 'Text'
|
86
|
+
|
87
|
+
#
|
88
|
+
# Link page
|
89
|
+
#
|
90
|
+
@link_url = Gtk::Entry.new
|
91
|
+
@link_name = Gtk::Entry.new
|
92
|
+
link_description = multiline_entry :link_description
|
93
|
+
|
94
|
+
page = Gtk::VBox.new false, 4
|
95
|
+
page.set_border_width 8
|
96
|
+
page.pack_with_label 'URL', @link_url
|
97
|
+
page.pack_with_label 'Name (optional)', @link_name
|
98
|
+
page.pack_with_label'Description (optional)', link_description, true
|
99
|
+
|
100
|
+
@notebook.add_page_with_tab page, 'Link'
|
101
|
+
|
102
|
+
#
|
103
|
+
# Chat page
|
104
|
+
#
|
105
|
+
@chat_title = Gtk::Entry.new
|
106
|
+
chat_conversation = multiline_entry :chat_conversation
|
107
|
+
|
108
|
+
page = Gtk::VBox.new false, 4
|
109
|
+
page.set_border_width 8
|
110
|
+
page.pack_with_label 'Title (optional)', @chat_title
|
111
|
+
page.pack_with_label 'Conversation', chat_conversation, true
|
112
|
+
|
113
|
+
@notebook.add_page_with_tab page, 'Chat'
|
114
|
+
|
115
|
+
#
|
116
|
+
# Quote page
|
117
|
+
#
|
118
|
+
@quote_source = Gtk::Entry.new
|
119
|
+
quote_quote = multiline_entry :quote_quote
|
120
|
+
|
121
|
+
page = Gtk::VBox.new false, 4
|
122
|
+
page.set_border_width 8
|
123
|
+
page.pack_with_label 'Quote', quote_quote, true
|
124
|
+
page.pack_with_label 'Source (optional)', @quote_source
|
125
|
+
|
126
|
+
@notebook.add_page_with_tab page, 'Quote'
|
127
|
+
|
128
|
+
#
|
129
|
+
# Photo page
|
130
|
+
#
|
131
|
+
filter = Gtk::FileFilter.new
|
132
|
+
filter.set_name "Images"
|
133
|
+
filter.add_mime_type "image/*"
|
134
|
+
|
135
|
+
@photo_source = Gtk::Entry.new
|
136
|
+
@photo_click_through_url = Gtk::Entry.new
|
137
|
+
photo_data = file_chooser_button :photo_data, filter
|
138
|
+
photo_caption = multiline_entry :photo_caption
|
139
|
+
|
140
|
+
page = Gtk::VBox.new false, 4
|
141
|
+
page.set_border_width 8
|
142
|
+
page.pack_with_label 'File', photo_data
|
143
|
+
page.pack_with_label 'Source', @photo_source
|
144
|
+
page.pack_with_label 'Caption', photo_caption, true
|
145
|
+
page.pack_with_label 'Link (optional)', @photo_click_through_url
|
146
|
+
|
147
|
+
@notebook.add_page_with_tab page, 'Photo'
|
148
|
+
|
149
|
+
#
|
150
|
+
# Audio page
|
151
|
+
#
|
152
|
+
if $api.user.can_upload_audio == '1'
|
153
|
+
filter = Gtk::FileFilter.new
|
154
|
+
filter.set_name "Audio"
|
155
|
+
filter.add_mime_type "audio/*"
|
156
|
+
|
157
|
+
audio_data = file_chooser_button :audio_data, filter
|
158
|
+
audio_caption = multiline_entry :audio_caption
|
159
|
+
|
160
|
+
page = Gtk::VBox.new false, 4
|
161
|
+
page.set_border_width 8
|
162
|
+
page.pack_with_label 'File', audio_data
|
163
|
+
page.pack_with_label 'Caption (optional)', audio_caption, true
|
164
|
+
|
165
|
+
@notebook.add_page_with_tab page, 'Audio'
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Video page
|
170
|
+
#
|
171
|
+
@video_embed = Gtk::Entry.new
|
172
|
+
video_caption = multiline_entry :video_caption
|
173
|
+
|
174
|
+
page = Gtk::VBox.new false, 4
|
175
|
+
page.set_border_width 8
|
176
|
+
|
177
|
+
if $api.user.can_upload_video == '1'
|
178
|
+
filter = Gtk::FileFilter.new
|
179
|
+
filter.set_name "Video"
|
180
|
+
filter.add_mime_type "video/*"
|
181
|
+
|
182
|
+
video_data = file_chooser_button :video_data, filter
|
183
|
+
@video_title = Gtk::Entry.new
|
184
|
+
|
185
|
+
page.pack_with_label 'File', video_data
|
186
|
+
page.pack_with_label 'Title (optional)', @video_title
|
187
|
+
end
|
188
|
+
|
189
|
+
page.pack_with_label 'Embed code / YouTube link', @video_embed
|
190
|
+
page.pack_with_label 'Caption (optional)', video_caption, true
|
191
|
+
|
192
|
+
@notebook.add_page_with_tab page, 'Video'
|
193
|
+
|
194
|
+
##
|
195
|
+
### Toolbar
|
196
|
+
##
|
197
|
+
|
198
|
+
toolbar = Gtk::Toolbar.new
|
199
|
+
toolbar.icon_size = Gtk::IconSize::MENU
|
200
|
+
|
201
|
+
icon = Gtk::Image.new Gtk::Stock::HOME, Gtk::IconSize::MENU
|
202
|
+
item = Gtk::ToolButton.new icon, 'Tumblelog'
|
203
|
+
item.signal_connect(:clicked) do
|
204
|
+
Thread.new { system('xdg-open "%s"' % $app.blog.url) }
|
205
|
+
end
|
206
|
+
toolbar.insert 0, item
|
207
|
+
|
208
|
+
icon = Gtk::Image.new Gtk::Stock::PREFERENCES, Gtk::IconSize::MENU
|
209
|
+
item = Gtk::ToolButton.new icon, 'Dashboard'
|
210
|
+
item.signal_connect(:clicked) do
|
211
|
+
Thread.new { system('xdg-open "http://www.tumblr.com/tumblelog/%s"' % $app.blog.name) }
|
212
|
+
end
|
213
|
+
toolbar.insert 1, item
|
214
|
+
|
215
|
+
combo = Gtk::ComboBox.new
|
216
|
+
active_blog = $cfg.get(:active_blog) || nil
|
217
|
+
active_blog_idx = nil
|
218
|
+
$api.blogs.each_with_index do |blog, idx|
|
219
|
+
combo.append_text blog.title
|
220
|
+
active_blog_idx = idx if blog.name.eql?(active_blog)
|
221
|
+
active_blog_idx = idx if active_blog_idx.nil? and blog.is_primary == "yes"
|
222
|
+
end
|
223
|
+
combo.signal_connect(:changed) do |widget|
|
224
|
+
$app.blog = $api.blogs[widget.active]
|
225
|
+
$cfg.set :active_blog, $app.blog.name
|
226
|
+
@statusbar.push 0, $app.blog.title
|
227
|
+
end
|
228
|
+
combo.set_active(active_blog_idx)
|
229
|
+
item = Gtk::ToolItem.new
|
230
|
+
item.set_expand true
|
231
|
+
item.add combo
|
232
|
+
toolbar.insert 2, item
|
233
|
+
|
234
|
+
icon = Gtk::Image.new Gtk::Stock::QUIT, Gtk::IconSize::MENU
|
235
|
+
item = Gtk::ToolButton.new icon, 'Quit'
|
236
|
+
item.set_homogeneous false
|
237
|
+
item.signal_connect(:clicked) do
|
238
|
+
$app.quit
|
239
|
+
end
|
240
|
+
toolbar.insert 3, item
|
241
|
+
|
242
|
+
##
|
243
|
+
### Buttons
|
244
|
+
##
|
245
|
+
clear_button = Gtk::Button.new 'Clear'
|
246
|
+
clear_button.set_focus_on_click false
|
247
|
+
clear_button.signal_connect(:clicked) do |widget|
|
248
|
+
page = @notebook.get_nth_page @notebook.page
|
249
|
+
message_type = @notebook.get_menu_label_text page
|
250
|
+
reset_form message_type.downcase
|
251
|
+
end
|
252
|
+
|
253
|
+
@private_button = Gtk::ToggleButton.new 'Private'
|
254
|
+
@private_button.signal_connect(:toggled) do |widget|
|
255
|
+
$cfg.set :private, widget.active?
|
256
|
+
end
|
257
|
+
@private_button.set_active $cfg.get(:private)
|
258
|
+
|
259
|
+
submit_button = Gtk::Button.new 'Send'
|
260
|
+
submit_button.signal_connect(:released) do |widget|
|
261
|
+
post
|
262
|
+
end
|
263
|
+
|
264
|
+
button_box = Gtk::HBox.new false, 4
|
265
|
+
button_box.pack_start clear_button, false
|
266
|
+
button_box.pack_start @private_button, false
|
267
|
+
button_box.pack_start submit_button
|
268
|
+
|
269
|
+
##
|
270
|
+
### Layout
|
271
|
+
##
|
272
|
+
pack_start toolbar, false
|
273
|
+
pack_start @notebook
|
274
|
+
pack_start button_box, false
|
275
|
+
pack_start @statusbar, false
|
276
|
+
show_all
|
277
|
+
end
|
278
|
+
|
279
|
+
def post
|
280
|
+
page = @notebook.get_nth_page @notebook.page
|
281
|
+
message_type = @notebook.get_menu_label_text(page).downcase
|
282
|
+
|
283
|
+
mandatory_data = collect_data_for Ppds::Tumblr::MANDATORY_FIELDS, message_type
|
284
|
+
concurent_data = collect_data_for Ppds::Tumblr::CONCURENT_FIELDS, message_type
|
285
|
+
optional_data = collect_data_for Ppds::Tumblr::OPTIONAL_FIELDS, message_type
|
286
|
+
|
287
|
+
mandatory_data.each do |key, value|
|
288
|
+
raise "Mandatory field %s is not set!" % key if not value or value.empty?
|
289
|
+
end unless Ppds::Tumblr::MANDATORY_FIELDS[message_type].empty?
|
290
|
+
|
291
|
+
unless Ppds::Tumblr::CONCURENT_FIELDS[message_type].empty?
|
292
|
+
concurent_data.delete_if { |x,y| y == "" or y.nil? }
|
293
|
+
raise "None of fields %s is set!" % Ppds::Tumblr::CONCURENT_FIELDS[message_type].join(", ") if concurent_data.empty?
|
294
|
+
end
|
295
|
+
|
296
|
+
optional_data.delete_if { |x,y| y == "" or y.nil? }
|
297
|
+
|
298
|
+
data = {
|
299
|
+
:generator => 'Grumblr',
|
300
|
+
:email => $cfg.get(:email),
|
301
|
+
:password => $cfg.get(:password),
|
302
|
+
:channel_id => $app.blog.name,
|
303
|
+
:group => $app.blog.name + '.tumblr.com',
|
304
|
+
:type => message_type,
|
305
|
+
:format => @format.active? ? 'markdown' : 'html',
|
306
|
+
:private => @private_button.active? ? 1 : 0
|
307
|
+
}
|
308
|
+
|
309
|
+
data.merge! mandatory_data
|
310
|
+
data.merge! concurent_data
|
311
|
+
data.merge! optional_data
|
312
|
+
|
313
|
+
data.update({:data => File.read(data['data'])}) if data.has_key?('data') and data['data'] != ""
|
314
|
+
|
315
|
+
$api.query 'write', data
|
316
|
+
MessageDialog.new "Message posted", Gtk::Stock::DIALOG_INFO
|
317
|
+
reset_form message_type
|
318
|
+
rescue Exception
|
319
|
+
MessageDialog.new $!
|
320
|
+
end
|
321
|
+
|
322
|
+
def collect_data_for(fieldset, message_type)
|
323
|
+
data = {}
|
324
|
+
for key in fieldset[message_type]
|
325
|
+
name = "@#{message_type}_#{key.gsub(/-/,'_')}"
|
326
|
+
if var = instance_variable_get(name)
|
327
|
+
value = var.get_value
|
328
|
+
data.merge!({ key => value })
|
329
|
+
end
|
330
|
+
end
|
331
|
+
data
|
332
|
+
end
|
333
|
+
|
334
|
+
def file_chooser_button(name, filter = nil)
|
335
|
+
button = Gtk::FileChooserButton.new('Open', Gtk::FileChooser::ACTION_OPEN)
|
336
|
+
if filter
|
337
|
+
button.add_filter(filter)
|
338
|
+
button.set_filter(filter)
|
339
|
+
end
|
340
|
+
button.signal_connect(:selection_changed) do |widget|
|
341
|
+
puts widget.filename
|
342
|
+
end
|
343
|
+
button.show_all
|
344
|
+
instance_variable_set "@#{name}", button
|
345
|
+
end
|
346
|
+
|
347
|
+
def multiline_entry(name)
|
348
|
+
instance_variable_set "@#{name}", Gtk::TextBuffer.new
|
349
|
+
|
350
|
+
view = Gtk::TextView.new
|
351
|
+
view.set_buffer instance_variable_get("@#{name}")
|
352
|
+
view.set_wrap_mode Gtk::TextTag::WRAP_WORD
|
353
|
+
view.set_right_margin 5
|
354
|
+
view.set_left_margin 5
|
355
|
+
|
356
|
+
window = Gtk::ScrolledWindow.new
|
357
|
+
window.set_shadow_type Gtk::SHADOW_IN
|
358
|
+
window.set_policy Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC
|
359
|
+
window.add view
|
360
|
+
window.show_all
|
361
|
+
end
|
362
|
+
|
363
|
+
def reset_fields_for(fieldset, message_type)
|
364
|
+
for key in fieldset[message_type]
|
365
|
+
name = "@#{message_type}_#{key.gsub(/-/,'_')}"
|
366
|
+
var = instance_variable_get(name)
|
367
|
+
var.clear if var
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def reset_form(message_type)
|
372
|
+
[ Ppds::Tumblr::MANDATORY_FIELDS,
|
373
|
+
Ppds::Tumblr::CONCURENT_FIELDS,
|
374
|
+
Ppds::Tumblr::OPTIONAL_FIELDS ].each do |fieldset|
|
375
|
+
reset_fields_for(fieldset, message_type)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
class MessageDialog < Gtk::Dialog
|
381
|
+
def initialize(text, stock = Gtk::Stock::DIALOG_ERROR)
|
382
|
+
super "Attention!", $gui, Gtk::Dialog::MODAL
|
383
|
+
|
384
|
+
message = Gtk::Label.new text
|
385
|
+
icon = Gtk::Image.new stock, Gtk::IconSize::DIALOG
|
386
|
+
|
387
|
+
hbox = Gtk::HBox.new false, 20
|
388
|
+
hbox.set_border_width 20
|
389
|
+
hbox.pack_start icon, false
|
390
|
+
hbox.pack_start message, true
|
391
|
+
|
392
|
+
self.add_button Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE
|
393
|
+
self.signal_connect(:response) { self.destroy }
|
394
|
+
self.vbox.add hbox
|
395
|
+
self.show_all
|
396
|
+
self.run
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
class SettingsFrame < Gtk::HBox
|
401
|
+
def initialize
|
402
|
+
super false, 40
|
403
|
+
self.set_border_width 40
|
404
|
+
|
405
|
+
@label = Gtk::Label.new
|
406
|
+
@label.set_markup '<b>Fill-in Tumblr credentials</b>'
|
407
|
+
|
408
|
+
@text_e = Gtk::Entry.new
|
409
|
+
@text_e.set_text $cfg.get(:email).to_s
|
410
|
+
|
411
|
+
@text_p = Gtk::Entry.new
|
412
|
+
@text_p.set_visibility false
|
413
|
+
@text_p.set_text $cfg.get(:password).to_s
|
414
|
+
|
415
|
+
hbox = Gtk::HBox.new
|
416
|
+
|
417
|
+
button = Gtk::Button.new 'Cancel'
|
418
|
+
button.signal_connect(:released) { $app.quit }
|
419
|
+
hbox.pack_start button
|
420
|
+
|
421
|
+
button = Gtk::Button.new 'Sign in'
|
422
|
+
button.signal_connect(:released) { login }
|
423
|
+
hbox.pack_start button
|
424
|
+
|
425
|
+
header = Gtk::Label.new
|
426
|
+
header.set_alignment 0.0, 0.8
|
427
|
+
header.set_markup '<big><big><b>Grumblr 2.0</b></big></big>'
|
428
|
+
|
429
|
+
vbox = Gtk::VBox.new false, 4
|
430
|
+
vbox.pack_start header
|
431
|
+
vbox.pack_with_label '_Email', @text_e
|
432
|
+
vbox.pack_with_label '_Password', @text_p
|
433
|
+
vbox.pack_start @label
|
434
|
+
vbox.pack_start hbox, false
|
435
|
+
|
436
|
+
logo = Gtk::Image.new $gui.logo
|
437
|
+
|
438
|
+
self.pack_start logo
|
439
|
+
self.pack_start vbox
|
440
|
+
self.show_all
|
441
|
+
end
|
442
|
+
|
443
|
+
def login
|
444
|
+
email = @text_e.text.strip
|
445
|
+
password = @text_p.text.strip
|
446
|
+
if $api.authenticate(email, password)
|
447
|
+
$cfg.set :email, email
|
448
|
+
$cfg.set :password, password
|
449
|
+
self.destroy
|
450
|
+
@dashboard = Dashboard.new
|
451
|
+
$gui.add @dashboard
|
452
|
+
else
|
453
|
+
raise "Authentication failed"
|
454
|
+
end
|
455
|
+
rescue Exception
|
456
|
+
MessageDialog.new $!
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
|
461
|
+
class AboutDialog < Gtk::AboutDialog
|
462
|
+
def initialize
|
463
|
+
Gtk::AboutDialog.set_email_hook do |dialog, email|
|
464
|
+
system("xdg-email #{email}")
|
465
|
+
end
|
466
|
+
Gtk::AboutDialog.set_url_hook do |dialog, url|
|
467
|
+
system("xdg-open #{url}")
|
468
|
+
end
|
469
|
+
super
|
470
|
+
self.logo = $gui.logo
|
471
|
+
self.program_name = 'Grumblr'
|
472
|
+
self.version = '2.0'
|
473
|
+
self.copyright = "Copyright (c)2009, Paul Philippov"
|
474
|
+
self.comments = "Tumblr companion for GNOME"
|
475
|
+
self.license = "New BSD License.\nhttp://creativecommons.org/licenses/BSD/"
|
476
|
+
self.website = "http://grumblr.googlecode.com/"
|
477
|
+
self.website_label = "grumblr.googlecode.com"
|
478
|
+
self.authors = ['Paul Philippov <paul@ppds.ws>']
|
479
|
+
self.run
|
480
|
+
self.destroy
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
class StatusIcon < Gtk::StatusIcon
|
485
|
+
def initialize
|
486
|
+
super
|
487
|
+
self.file = File.join(Grumblr::IMAGE_ROOT, 'grumblr.svg')
|
488
|
+
self.tooltip = "Application Name Goes Here"
|
489
|
+
self.signal_connect(:activate) do
|
490
|
+
if $gui.visible?
|
491
|
+
$gui.minimize
|
492
|
+
else
|
493
|
+
$gui.move $cfg.get(:window_x_pos), $cfg.get(:window_y_pos)
|
494
|
+
$gui.show.present
|
495
|
+
end
|
496
|
+
end
|
497
|
+
self.signal_connect(:popup_menu) do |icon, button, time|
|
498
|
+
menu.popup nil, nil, button, time
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def menu
|
503
|
+
menu = Gtk::Menu.new
|
504
|
+
for item in [ ontop, sep, destroy_account, sep, about, sep, quit ]
|
505
|
+
menu.append item
|
506
|
+
end
|
507
|
+
menu.show_all
|
508
|
+
end
|
509
|
+
|
510
|
+
def sep
|
511
|
+
Gtk::SeparatorMenuItem.new
|
512
|
+
end
|
513
|
+
|
514
|
+
##
|
515
|
+
## Destroy Config
|
516
|
+
##
|
517
|
+
def destroy_account
|
518
|
+
icon = Gtk::ImageMenuItem.new 'Destroy account'
|
519
|
+
icon.set_image Gtk::Image.new(Gtk::Stock::STOP, Gtk::IconSize::MENU)
|
520
|
+
icon.signal_connect(:activate) do
|
521
|
+
$cfg.destroy
|
522
|
+
end
|
523
|
+
icon.show
|
524
|
+
end
|
525
|
+
|
526
|
+
def about
|
527
|
+
icon = Gtk::ImageMenuItem.new Gtk::Stock::ABOUT
|
528
|
+
icon.signal_connect(:activate) do
|
529
|
+
AboutDialog.new
|
530
|
+
end
|
531
|
+
icon.show
|
532
|
+
end
|
533
|
+
|
534
|
+
def ontop
|
535
|
+
icon = Gtk::CheckMenuItem.new 'Always on top'
|
536
|
+
icon.signal_connect(:toggled) do |widget|
|
537
|
+
$gui.keep_above = widget.active?
|
538
|
+
end
|
539
|
+
icon.show
|
540
|
+
end
|
541
|
+
|
542
|
+
def quit
|
543
|
+
icon = Gtk::ImageMenuItem.new Gtk::Stock::QUIT
|
544
|
+
icon.signal_connect(:activate) do
|
545
|
+
$app.quit
|
546
|
+
end
|
547
|
+
icon.show
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
class Gtk::Box
|
553
|
+
def pack_with_label(text, widget, expand = false)
|
554
|
+
label = Gtk::Label.new text, true
|
555
|
+
label.set_alignment 0.0, 0.5
|
556
|
+
label.set_mnemonic_widget widget
|
557
|
+
self.pack_start label, false
|
558
|
+
self.pack_start widget, expand
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
class Gtk::Entry
|
563
|
+
alias :get_value :text
|
564
|
+
def clear
|
565
|
+
self.set_text ''
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
module Gtk::FileChooser
|
570
|
+
alias :get_value :filename
|
571
|
+
alias :clear :unselect_all
|
572
|
+
end
|
573
|
+
|
574
|
+
class Gtk::Notebook
|
575
|
+
def add_page_with_tab(page, text)
|
576
|
+
filename = File.join(Grumblr::IMAGE_ROOT, '%s.bmp' % text.downcase)
|
577
|
+
icon = Gtk::Image.new filename
|
578
|
+
icon.set_padding 2, 4
|
579
|
+
|
580
|
+
label = Gtk::Label.new '_' + text, true
|
581
|
+
label.set_alignment 0.0, 0.5
|
582
|
+
label.set_padding 4, 2
|
583
|
+
|
584
|
+
box = Gtk::HBox.new false, 4
|
585
|
+
box.pack_start icon, false
|
586
|
+
box.pack_start label, true
|
587
|
+
box.show_all
|
588
|
+
|
589
|
+
self.append_page_menu page, box, label
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
class Gtk::TextBuffer
|
594
|
+
alias :get_value :get_text
|
595
|
+
def clear
|
596
|
+
self.set_text ''
|
597
|
+
end
|
598
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ppds
|
2
|
+
class ClassFactory
|
3
|
+
def initialize(data=[])
|
4
|
+
for att in data
|
5
|
+
set(att.name.gsub(/-/,'_'), att.value)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(name)
|
10
|
+
instance_variable_get("@#{name}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(name, value)
|
14
|
+
instance_variable_set("@#{name}", value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ppds/config.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'gconf2'
|
2
|
+
|
3
|
+
module Ppds
|
4
|
+
class Config
|
5
|
+
@@gconf = GConf::Client.default
|
6
|
+
|
7
|
+
def gconf
|
8
|
+
@@gconf
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@root = '/apps/%s' % app
|
13
|
+
gconf.add_dir(@root)
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_from_key(key)
|
17
|
+
key.split('/').last
|
18
|
+
end
|
19
|
+
|
20
|
+
def key_from_name(name)
|
21
|
+
[ @root, name ].join("/")
|
22
|
+
end
|
23
|
+
|
24
|
+
def all
|
25
|
+
gconf.all_entries(@root)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(name)
|
29
|
+
gconf[key_from_name(name)]
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(name, value)
|
33
|
+
gconf[key_from_name(name)] = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
gconf.suggest_sync
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy
|
41
|
+
for one in all
|
42
|
+
gconf.unset(one.key)
|
43
|
+
end
|
44
|
+
save
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/ppds/tumblr.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'ppds/class_factory'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'xml'
|
4
|
+
|
5
|
+
module Ppds
|
6
|
+
class User < ClassFactory
|
7
|
+
attr_accessor :default_post_format, :can_upload_audio, :can_upload_aiff,
|
8
|
+
:can_upload_video, :vimeo_login_url, :max_video_bytes_uploaded
|
9
|
+
end
|
10
|
+
|
11
|
+
class Blog < ClassFactory
|
12
|
+
attr_accessor :type, :title, :name, :url, :avatar_url,
|
13
|
+
:is_primary, :private_id
|
14
|
+
end
|
15
|
+
|
16
|
+
class Tumblr
|
17
|
+
attr_accessor :user, :blogs
|
18
|
+
|
19
|
+
MANDATORY_FIELDS = {
|
20
|
+
'text' => ['body'],
|
21
|
+
'link' => ['url'],
|
22
|
+
'chat' => ['conversation'],
|
23
|
+
'quote' => ['quote'],
|
24
|
+
'photo' => [],
|
25
|
+
'video' => [],
|
26
|
+
'audio' => ['data']
|
27
|
+
}
|
28
|
+
|
29
|
+
CONCURENT_FIELDS = {
|
30
|
+
'text' => [],
|
31
|
+
'link' => [],
|
32
|
+
'chat' => [],
|
33
|
+
'quote' => [],
|
34
|
+
'photo' => ['source', 'data'],
|
35
|
+
'video' => ['embed', 'data'],
|
36
|
+
'audio' => []
|
37
|
+
}
|
38
|
+
|
39
|
+
OPTIONAL_FIELDS = {
|
40
|
+
'text' => ['title'],
|
41
|
+
'link' => ['name', 'description'],
|
42
|
+
'chat' => ['title'],
|
43
|
+
'quote' => ['source'],
|
44
|
+
'photo' => ['caption','click-through-url'],
|
45
|
+
'video' => ['caption'], # 'title'
|
46
|
+
'audio' => ['caption']
|
47
|
+
}
|
48
|
+
|
49
|
+
API_URL = 'http://www.tumblr.com/api/'
|
50
|
+
|
51
|
+
def initialize
|
52
|
+
self.user = nil
|
53
|
+
self.blogs = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def query(action, data)
|
57
|
+
RestClient.post(API_URL + action, data)
|
58
|
+
rescue RestClient::RequestFailed
|
59
|
+
raise 'Query failed: %s' % $!
|
60
|
+
rescue RestClient::RequestTimeout
|
61
|
+
raise 'Timeout occured'
|
62
|
+
rescue Exception
|
63
|
+
raise $!
|
64
|
+
end
|
65
|
+
|
66
|
+
def authenticate(email, password)
|
67
|
+
data = {
|
68
|
+
:email => email,
|
69
|
+
:password => password
|
70
|
+
}
|
71
|
+
xml = query('authenticate', data)
|
72
|
+
@xml = XML::Parser.string(xml).parse
|
73
|
+
|
74
|
+
self.user = User.new(@xml.find_first('//tumblr/user').attributes) if @xml
|
75
|
+
self.blogs = @xml.find('//tumblr/tumblelog').map do |node|
|
76
|
+
Blog.new(node.attributes)
|
77
|
+
end
|
78
|
+
|
79
|
+
true
|
80
|
+
|
81
|
+
rescue Exception
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: themactep-grumblr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Philippov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-22 00:00:00 -07:00
|
13
|
+
default_executable: grumblr
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: libxml-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rest-client
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Grumblr is a message poster to Tumblr blogs from GNOME.
|
36
|
+
email: themactep@gmail.com
|
37
|
+
executables:
|
38
|
+
- grumblr
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- LICENSE
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- bin/grumblr
|
51
|
+
- data/grumblr.desktop
|
52
|
+
- grumblr.gemspec
|
53
|
+
- images/audio.bmp
|
54
|
+
- images/chat.bmp
|
55
|
+
- images/grumblr.svg
|
56
|
+
- images/link.bmp
|
57
|
+
- images/photo.bmp
|
58
|
+
- images/quote.bmp
|
59
|
+
- images/spinner.gif
|
60
|
+
- images/text.bmp
|
61
|
+
- images/video.bmp
|
62
|
+
- lib/grumblr/core.rb
|
63
|
+
- lib/grumblr/ui.rb
|
64
|
+
- lib/ppds/class_factory.rb
|
65
|
+
- lib/ppds/config.rb
|
66
|
+
- lib/ppds/tumblr.rb
|
67
|
+
has_rdoc: false
|
68
|
+
homepage: http://github.com/themactep/grumblr
|
69
|
+
licenses:
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options:
|
72
|
+
- --charset=UTF-8
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.5
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Tumblr companion for GNOME
|
94
|
+
test_files: []
|
95
|
+
|